2008年12月7日星期日

自己对程序员的分级

自己闲的无聊为计算机专业的学生定的级别,从D一直到X,从低到高,偶是C系程序员,故编码都是C系的技术,J系的类比就是了,呵呵

D-:
知道一些计算机的基本操作,能够说明白内存和硬盘的区别,知道CPU既有Intel的,也有AMD的,上网很多,但没有信息抓取和搜索能力,只是被动的接收信息。

D:
可以比较熟练用某种语言编写一个"Hello World",了解程序控制结构,可以通过一堆if、while、for、switch,在一个方法(函数)里实现一个基本的功能,如果代码通不过编译的话会认为是编译器出错,如果代码通过编译而且正巧结果还对,就认为已经完成程序了。知道栈和队列这些基本数据结构的概念,但是无法完美的去用某种语言实现。

D+:
知道程序不仅仅可以在一个方法体里写,有了初步的函数的思想,可以完成一些行数较多的程序(大于200行),不过这个程序一般只有编译器和他本人能看懂(比如string s1,s2,s3;这样的语句)。终于开始相信自己有时也会犯错误的(对编译器多了点信任),写完了程序会做一点基本的测试,不过输入的数据都是比较"完美"的数据,认为没有必要用Dirty Data(脏数据)进行测试,这样只会给自己增加烦恼。

C-:
对数据结构,操作系统这样的基础课程有了一个初步的认识,可以在20分钟内写一个带有Bug的二分搜索或是含糊的描述出来进程和线程的区别。具备了"分治"(Divide and conquer)和递归(Recursive)的初步思想。知道什么是Pascal风格和camel风格,听说过Hungarian命名法,不过只是偶尔的在自己的代码去运用这种命名法。知道了程序是会抛出异常的,所以就用try/catch all这样的风格去写程序,认为没有异常的程序就是好程序

C:
具备了一定的代码量(超过5k行),至少对某一门主流的编程语言有了一定的认识,开始从结构化编程向对象化编程转变,可以从容的说出OO三特性是封装(Encapsulation),继承(Inheritance)和多态(Polymorphism),但是却很难为多态给出一个精准的定义。在写中型程序的时候会有意的建立一个class,然后"疯狂"的去丰富这个class的功能并最终完成一个典型的GodClass。认为数据结构操作系统这类的课程是在浪费时间,反正我们有的是类库,现成的List/Hashtable/Queue满天飞,需要用的时候引一下就是了。开始用搜索引擎协助自己编写代码,并会比较熟练的使用断点。

C+:
具备了一定的代码量(超过15k行),对某一门主流的编程语言有了比较深入的认识,拿C#来说,可以描述出中的属性(Property)的实现方式,指出值类型和引用类型的五个区别或是给出十个不同类型的Exception,知道有些数据存储在线程栈(Thread Stack)上而有些数据存储在托管堆(Managed Heap)上而存取它们的速度是很不一样的,拿C++来说,可以精确的给出常量指针和指针常量的区别及各自的用途,或是描述C++中实现多态的方式(VTable)。开始有意的去使用多态的思想编写程序但并没有意识到这样做的实际好处,开始使用一些接口(interface)并试图将具有同样行为的类拉拢在一起。

B-:
具备了一定的代码量(超过30k行),对某一门主流的编程语言有了深入的认识,拿C#来说,可以精确的描述出new操作符的具体的行为(计算类型到基类型需要的空间并分配这部分内存,初始化类型对象指针和同步块索引,调用类型的实例构造器创建实例,返回实例的一个引用),清楚的知道该什么时候使用struct而什么时候该使用class。开始在程序中应用设计模式的思想,编写代码的过程中将思考的问题更多的停留在代码的易读性和高效性的抉择上。能够比较有效的利用网络资源去获得自己需要的信息(尤其是熟练的使用英文的搜索引擎)。

B:
具备了一定的代码量(超过50k行),在对一门主流的编程语言有了深入的认识的基础上,在数据结构和操作系统等计算机基础课程的也有了一定的造诣。例如可以轻松的给出6种不同的排序算法并可以在段时间之内将它们以某一种语言实现,也可以在1分钟之内写出一个准确的二分搜索算法。可以在短时间内理解某个API,然后在这个API的基础之上根据自己的需求写一个更加实用的Wrapper。很自然的应用Factory Method去替代Constructor。熟悉异常的使用,拿.Net来说,既知道FCL多数常用的方法会在什么情况下抛出什么样的异常而在什么情况下不会抛异常,也知道在自己编写的类库中什么时候应该抛出什么样的异常。

2008年11月16日星期日

CLR via C#学习笔记(6)--强制类型转换

CLR中很重要的特性之一就是类型安全性,也就是说,在运行的时候,CLR总是知道一个对象的类型。如果想知道一个对象的确切类型,可以去调用GetType方法,由于这个方法是非虚方法,不可被重写,所以一个类型不能伪装成另一种类型。

开发人员经常需要将一个对象从一种类型转换成其它类型。CLR允许将一个对象强制转换成它的类型或者是它的任何基类型。CLR中的每种编程语言都规定了具体如何将转型操作揭示给开发人员。比如说,C#不需要任何特殊的语法就可以将一个对象转换成任何它的基类型,而C#要求开发人员将对象转换成它的任何派生类型这个操作必须是显式的,因为这种类型有可能在运行时失败。

internal class Employee
{...}

public sealed class Program
{
public static void Main()
{
//不需要转型,因为new返回一个Employee对象,而Employee是Object的派生类型
Object o=new Employee();
//需要转型,因为Employee从Object派生
Employee e=(Employee)o;
//下面的操作可以通过编译,但是在运行时会抛出InvalidCastException异常
//因为dt既不是Employee类型,也不是Employee的派生类型
DateTime dt=DateTime.Now;
Employee e1=(Employee)dt;
}
}

在C#语言中进行强制类型转换的另外一种比较"温和"的方式是使用is操作符,is操作符检查一个对象是否兼容于指定的类型,并返回一个Boolean值。之所以说它比较"温和",是因为is操作符不会抛出异常。is操作符经常这么使用:

if(o is Employee)
{
Employee e=(Employee)o;
}

不过上面的代码CLR实际上检查了两次对象的类型,is操作符首先检测o是否兼容于Employee类型,如果是肯定的,那么在if语句内部执行强制转型时,CLR会再次核实o是否引用一个Employee,CLR的类型检查增强了安全性,但无疑也会对性能造成一定的影响。也因为CLR首先需要判断o引用的对象的实际类型,然后,CLR会遍历继承层次结构,用每个基类型去核对指定的类型(Employee)。由于这种编程模式很常见,所以C#提供了as操作符,目的就是简化这种模式的代码写法,同时提升性能。

Employee e=o as Employee;
if(e!=null)
{
//Using e
}

as与is一样,它不会抛出一个异常——只要对象不能成功转型,它的结果就是null。当然如果不加判断直接使用最终生成的引用的话,可能会造成一个System.NullReferenceException异常。

2008年11月12日星期三

设计模式解析-学习笔记(2)--功能分解

编程中有一种很重要的思想,就是分而治之(Divide and Conquer),N多的算法都采取了这种思想,比如Merge Sort和Quick sort,以及对Binary tree的递归前中后的Traverse。套用到数学公式里,比如说工作F的内容包含F1和F2,那么F1+F2小于F,也就是说将F1和F2分开处理的难度要比直接处理F的难度要小。

在软件开发中也常用到这种思想,不过这里叫做功能分解(Functional decomposition),比如说,如果给你一个任务,需要编写一段代码,访问数据库中存储的形状然后将其显示出来。按照正常所需要的步骤来思考是一种很自然的选择,比如说,你可能会这样分步骤解决这个问题:

1)在数据库中找到形状列表
2)打开形状列表
3)按某种规则将列表排序
4)在显示器上显示各个形状

然后就是逐步细化的过程,比如说步骤4可以被分解为下面的步骤:

4a)识别形状的类型
4b)识别形状的位置
4c)调用相应的方法,以位置作为参数,来显示形状

这就是一种很典型的"功能分解"的思想,因为分析人员把一个大的问题分解成了多个相对小的功能步骤(这些步骤就构成了这个问题的解决方案)。一般人都会这么做,因为直接解决整个问题的难度往往要比逐个解决子问题的难度要很多。

然而功能分解方法存在一个很大的问题:它通常会导致一个"主"程序负责控制子程序,当然这是将功能分解为多个子功能的自然结果。但是,主程序所承受的责任太多了,它不仅要确保一切子功能的正确工作,而且还要协调各个函数并控制它们的先后顺序,因此经常会产生非常复杂的代码。如果让一些子函数自己去负责自己的行为,并且可以告知主函数它们要去执行哪些任务,这种方式要比功能分解的方式容易得多。这就是委托(Delegation)的思想。

功能分解的另外一个问题在于:它很难应对未来可能出现的变化以及对代码进行合理的改进。变化是无法避免的:新功能的增加和新模型的引进都会导致代码发生变化。如果把实现各个步骤的所有逻辑代码都放在一个大模块中的话,那么这些步骤中哪怕是有一处变化,都会导致对整个模块进行修改。其实最麻烦的是不在于修改代码,而是在于变化往往会产生各种各样的Bug,也正如本书作者Alan Shalloway所提到的,许多Bug都源自与对代码的修改。

而且,无论事先的分析做的怎么好,也是无法从用户那里获得所有需求的,因为未来的变数太多了。因此对于阻止变化,我们是无计可施的,但是幸运的是我们对变化本事并不是无能为力的。

2008年11月8日星期六

计算机组成学习笔记(5)--RISC 和CISC

人们很早就发现,虽然复杂的指令单条执行的时间要长一些,但是却能使整个程序运行的更快,原因在于:执行指令的步骤有时可以重叠,或者用不同的硬件并行执行。所以高性能计算机的指令一般要多一些。到了20世纪50年代后期,当时占统治地位的IBM意识到支持一个所有计算机都执行相同指令的计算机序列是十分重要的,无论是对IBM自己还是IBM的用户都具有很多的优势,于是IBM引入了体系结构这个属于来描述这一个层次的兼容性。他们希望同系列的计算机的体系结构相同,可以用不同的实现的方法来执行同样的指令,只是在价格和速度上有所不同。

但是如何是低成本的计算机可以执行高价高性能的计算机的复杂指令呢?答案就是Cambridge的Wilkes在1951年提出的关于解释的理念。这个理念成就了IBM System/360体系结构的一系列兼容计算机,从通过解释来执行复杂指令的低端机到不通过解释直接由硬件实现复杂指令的高端机,IBM形成了一条完备的产品线。

解释执行指令的简单计算机有很多的优势。首先,可以在解释过程中改正指令实现的错误,甚至是补偿基础硬件中的设计缺陷;其次,可以以最小的代价来增加新的指令,甚至是在计算机发货之后也可以做到这一点;再次就是结构化的设计使对复杂的指令有效的进行开发、测试和编制文档成为可能。

到了20世纪70年代的后期,人们发现几乎所有的复杂的指令都可以被解释,设计者于是开始试图弥补高级语言和机器语言之间的“语义代沟”,几乎没人想到设计一台简单点的计算机,就像现在很少人去研究设计功能简练些的操作系统一样。

到了80年代,Berkeley的一个研究小组开始研究不用解释器的超大规模集成电路CPU芯片,并首次创造了RISC这个概念,并将他们的CPU命名为RISC I,接着又推出了RISC II。这种处理器和当时的商业处理器有着很大的区别,新的CPU并不存在与过去的产品间的兼容性问题,而且它可以自由选择新的指令集从而最大限度的提高系统的整体性能。由于设计的初期就强调选用可以快速执行的简单指令,大家很快意识到设计出可以快速启动的指令才是提高性能的关键,每秒钟能启动的指令的条数要比单条指令执行时间更重要。

实际上RISC是Reduced Instruction Set Computer的缩写,而与之对应的CISC则指代Complex Instruction Set Computer。RISC的支持者认为 即使RISC需要4条或5条指令来完成CISC的一条指令的功能,但是如果RISC能比CISC快10倍的话(因为RISC并不需要解释执行),那么还是RISC的速度占优。另外,随着主存的速度越来越快,解释执行的代价已经高了很多。

也许有人会想,依靠RISC在速度上的优势,RISC机(Sun的UltraSPARC)应该已经在市场上超出CISC机了(Intel的Pentium),但事实上并非如此,这是为什么呢?

首先由于向后兼容的问题,要考虑到N多的公司在Intel系列的CPU上投资的数十亿美元的软件,同时要注意到,Intel在它的CISC架构中也采用了RISC的思想,从486开始,Intel的CPU就包含了RISC核心,能在单个数据通路周期中执行一些最简单也是最常用的指令,同时复杂的指令还是用CISC的方式执行的,这样一来,常用的指令执行速度很快,而不常见的指令执行就会慢一些。显然这种混合的方案不如纯RISC方案快,但是它却可以在不加修改旧程序的前提下给出极具竞争力的整体性能。

2008年11月5日星期三

设计模式解析-学习笔记(1)--引言

其实在大三的时候,就已经听说过Design Pattern这个概念了,只不过当时只是知道有这个名词的存在,而并不知道它的具体含义。

后来搞了一本Head First Design Pattern,断断续续的看了前几章,由于当时并没有实际的编码经验,所以看到Command Pattern的时候就卡壳了。在那个时候只使用过Singleton Pattern,而且用的还是那种最原始的实现方式。至于Factory Method啊,Strategy Pattern这些东西,只是在和同学扯淡的时候提提而已。

大三结束后就是实习了,实习让我有了从来没有过的编程经验,我敢说我大学前三年编的代码的总和也超不过5k行,而实习第一个月怎么也得突破10k了。可见我平时在学校的腐败生活… 面壁去。

随着实习工作的不断深入,接触到的东西也越来越多,我惊奇的发现在编写代码的时候,我开始领悟到一些设计模式的理念,比如子类延迟初始化、依赖倒置、抽象算法族、封装变化之类的思想。而且也发现,客户的需求总是在变化的,自己编写的代码灵活度太低,添加一点功能就得修改一大片,根本满足不了要求。

于是我又回想到了设计模式、敏捷开发这些曾经接触过的名词,正好在公司有几天不是很忙,就在闲暇的三天看完了Judith Bishop的那本C# 3.0 Design Patterns并敲完了上面大部分的代码,总算是对3大类23种设计模式有了一个初步的了解。

然而感觉这样有点太快了些,必须得承认自己的实力实在有限,短时间还没办法把设计模式这玩意彻底贯彻成大脑里的一种编程思想甚至是习惯,于是决定再来一遍,当然这次就不能再看那本书了(C# 3.0 Design Patterns这本书可以让人短时间了解设计模式在C#中的应用,但要提及思想的话,就差的有点远了),挑选了下,决定还是看这本设计模式解析第二版,毕竟是03年Jolt的得主。

在一开始,我先总结下自己现在对优秀的代码的理解吧:高内聚,低耦合,易扩展,易追踪错误,易读。而且我一直认为就像经济学、生物学这些学科一样,编程也是分微观和宏观的,可以把微观的编程看作为对具体编程语言的理解和应用,而把宏观的编程看作为对实际工程项目中对象的抽象和建模。不知道在看完这本书后这个理解会不会有变化。

引言到此结束,下把开始正式的内容。

2008年11月4日星期二

CLR via C#学习笔记(5)--System.Object和new

CLR via C#的第二章和第三章讲了大量的生成和部署.Net应用程序的知识,里面涉及到了大量我从来没接触过的知识,于是决定先跳到和自己现在工作联系比较紧密的第二部分,也就是类型的基础,回头再看什么强名称、程序集之类的内容。

.Net运行库要求所有的类型都最终从System.Object类型派生,所以,可以保证每个类型的每个对象都拥有一套最基本的方法,下面是System.Object提供的方法。

首先来说说它提供的公共实例方法:

Equals:如果两个对象具有相同的值,则它返回true。

GetHashCode:返回调用该方法的对象的值得一个哈希码。

ToString:默认情况下,返回的是该对象类型的完整名称(this.GetType().FullName)。然而通常需要重写这个方法,使它返回一个更有意义的字符串对象。

GetType:返回由Type派生的对象实例,它表示用于调用GetType对象的类型。返回的Type对象可以和反射类配合使用,从而可以获取与类型有关的元数据信息。需要注意的是这个方法是一个非虚方法,这样可以防止某个类重写该方法,并隐藏实际的类型,从而破坏类型的安全性。

然后说说它的受保护方法:

MemberwiseClone:这个非虚方法可以创建类型的一个新实例,并将新对象实例的各个字段设置成与this对象实例的字段完全一致。注意它返回的是对新实例的一个引用。

Finalize:在GC判断对象该作为垃圾收集之后,在对象的内存被实际回收之前,会调用这个虚方法。需要在回收之前执行一些清理工作的类型(比如SqlConnection、FileStream)应该重写这个方法。

CLR要求所有的对象都需要使用new操作符来创建对象,比如下面的语句:

Employee e=new Employee("ConstructorParam1");

接下来说说new操作符所具体做的事情:

1,首先它计算类型及其所有基类型(最高到System.Object,虽然它并没有定义自己的实例字段)中所定义的所有实例字段所需要的字节数。堆上的每一个对象都需要一些额外的成员——称为"类型对象指针(Type Object Pointer)"和"同步块索引(Synchronous Block Index)",这些成员将由CLR来管理对象,这些额外成员的字节数会加到对象大小上。

2,之后它从托管堆(Managed Heap)中分配制定类型所需的字节数,从而分配对象的内存,分配的所有字节都设为零。

3,然后初始化对象的"类型对象指针"和"同步块索引"成员。

4,调用类型的实例构造方法,向其传入在new调用中制定的任何实参(也就是"ConstructorParam1")。大多数编译器都在构造方法中自动生成代码来调用一个基类的构造方法。这样在调用每个类型的构造方法的时候,构造方法都负责初始化由这个类型定义的实例字段,因此最终会调用到System.Object的构造方法,不过这个方法仅仅是简单的返回,并不去做其他任何事情。

new在执行了上面的所有的操作后,会返回对新创建的对象的一个引用(或者是指针),在前面的实例代码中,这个引用会保存到变量e中,后者则具有Employee类型。

顺便说一下,与C++不同,new操作符并没有对应的delete操作符,也就是说,没有办法显式释放为一个对象分配的内存。不过CLR采用了垃圾回收机制(GC),它可以自动检测到一个对象不再被使用或访问,并自动释放对象的内存。

2008年11月3日星期一

经济学原理学习笔记(5)--两个经济模型

就像高中生物教师会通过塑料人体模型来讲授基础解剖学一样,经济学家也用模型来了解这个世界,虽然模型并不是真实的,但是由于模型去除了很多并不重要的细节,从而使我们可以将注意力集中到重要的方面上来。所有的模型都建立在一些假设之上,就像物理学家通过假设不存在空气阻力来分析石头的下落一样,经济学家也使用假设来撇开与所研究问题无关的许多经济细节。

首先提到的模型叫做循环流量图(Circular Flow Diagram)。在这个模型中,经济由两类决策者——家庭和企业——所组成。企业用劳动、土地和资本(建筑物和机器)这些投入来生产物品和劳务。这些投入被称为生产要素。家庭则拥有生产要素并消费企业生产的所有物品和劳务。



家庭和企业在两类市场上相互交易。在物品与劳务市场上,家庭是买者,而企业是卖者,特别是家庭购买企业生产的物品与劳务。而在生产要素市场上,家庭是卖者而企业是买者。在这些市场上,家庭和企业提供用于生产物品与劳务的投入。循环流量图提供了一种把家庭与企业之间发生的所有经济交易组织在一起的简单方法。


第二个提到的模型叫做生产可能性边界,它是用来阐明一些基本的经济思想。

生产可能性边界(Production Possiblities Frontier)是一个图形,它表明在生产要素和生产技术既定时,一个经济所能生产的产品——在下面的例子中是汽车和电脑——数量的各种组合。



下图是生产可能性边界的一个例子:如果这个经济把全部的资源都用于汽车行业,那么它可以生产1000辆汽车而不生产电脑;如果全部用于电脑行业,那么它可以生产3000台电脑而不生产汽车。生产可能性边界的两个端点代表这两种极端的可能性。

当然更为可能的情况是经济把资源分在两个行业中,于是在生产可能性边界上就存在了其他的点。这些点组成了生产可能性边界。

如果经济从它可以获得的稀缺资源中获得了它能得到的全部东西,就称为这种结果是有效率的。在生产可能性边界上的各点代表了有效率的生产水平。而边界内部的点代表无效率的结果,这可能书出于普遍失业的原因,经济的产量小于他从可以获得的资源中所能得到的最大可能产量。由于资源是稀缺的,在其它条件不变的基础上,边界外的点是不能实现的。

CLR via C#学习笔记(4)--CTS和CLS

继续上回提到的CTS,CTS还提到了类型可视性规则以及类型成员的访问规则。比如,任何程序集都能看到并访问一个公共类型,另一方面,将一个类型标记为程序集的时候,这个类型只有在同一个程序集的内部才是可见的,而且只能从这个程序集的内部来访问。因此也可以这么理解:通过CTS制定的规则,程序集为一个类型建立了一个可视化的边界,同时CLR则用于来强制维护这些可视性规则。调用者可见的一个类型可以进一步限制调用者访问同类型成员的能力,下面列出了用于控制成员访问的各种有效选项:

private: 方法只能由同一个class中的其他方法去调用

family: 方法可由派生类型调用,无论那些类型是否在同一个程序集中(C#中用protected来标识family)

family和 assembly: 方法可由派生类型调用,但这些派生类型必须是在同一个程序集中定义的(注意是"和",像C#和VB中并没有提供这样的访问控制)

assembly: 方法可由同一个程序集中的任何代码调用(C#中用internal来标识assembly)

family和assembly: 方法可由任何程序集中的派生类型调用,也可由同一程序集中的任何类型来调用(C#中使用protected internal来标识"family或assembly")

public: 方法可由任何程序集的任何方法调用

同时,CTS还定义了对类型继承、虚方法、对象生存期等进行管理的规则。

接下来说说CLS吧。

COM允许用不同语言创建的对象相互通信。另一方面,CLR集成了所有语言,并允许在一种语言中使用由另外一种语言创建的对象。之所以可以做到这样的集成,是因为CLR建立了标准的类型集、元数据(自描述性的类型信息)以及公共执行环境。

虽然语言间的集成是一个很不错的目标,但是这中间存在一个很大的问题:各种编程语言之间有很大的区别,比如说VB在处理符号的时候并不区分大小写,而有的语言不支持unsigned类型、操作符重载。

所以说,如果想要创建很容易从其他编程语言中访问的类型,那么在自己的编程语言中提供的所有特性中,只能选用其他所有语言都保证支持的那一些。为了做到这一点,MS定义了一个Common Language Specification,它详细的定义了一个最小特性集。对于任何编译器厂商的编译器产品所生成的类型,如果他们要兼容位于CLR顶部的、支持CLS的其他语言,就必须得支持这个最小的特性集。

CLR/CTS支持的特性要比CLS定义的子集丰富的多,所以,如果不关心语言之间的互操作性的话,完全可以开发一套非常丰富的类型,而且这些类型仅受语言的特性集的限制。要注意的是:外部可见的类型和方法如果要从任何CLS相容的编程语言访问,就必须遵从CLS定义的规则。当然,如果代码只需要从定义程序集的内部访问,CLS规则就不适用了。

CLR/CTS提供了一个特性集:多数语言只是揭示了CLR/CTS的一个子集,如果程序员用IL来编写程序的话,那么他可以使用到CLR/CTS提供的全部特性,但是考虑到这种复杂性,大多数开发人员还是倾向于C#,VB这类语言,不过,C#、VB只向开发人员揭示了CLR/CTS的一个子集。

2008年11月1日星期六

经济学原理学习笔记(4)--科学的看待经济学

每个研究领域都有自己的语言和思考方式。数学家讨论公理、积分和向量空间,心理学家谈论自我、本我和认知的不一致性。经济学家也一样,供给、弹性、需求、比较优势、消费者剩余和无畏损失这类术语都是经济学家语言的一部分。

经济学作为一门科学这似乎有些不可思议,但是,科学的本质是科学方法:冷静的建立并校验有关时间如何运行的各种理论,这种研究方法适合于研究生物进化或者是地心引力,同样适用于研究一个国家的经济。

简单的说,科学方法就是:观察、理论和进一步的观察,例如牛顿通过观察苹果落地的现象进行理论然后发现了地心引力。虽然经济学家像其他科学家一样运用理论和观察,但是要记住在经济学中做实验是很困难的。研究万有引力的物理学家可以在实验室中自由下落各种物体从而得到实验数据,但是研究通货膨胀的经济学家绝不会被允许仅仅是为了获得有用的数据而去控制一国的货币供给。与天文学家、考古学家相类似,经济学家通常不得不使用这个世界碰巧向他们提供的数据。

如果去咨询一位物理学家一块石头从10米高空落下来需要多长时间,他会通过假设这块石头在真空落下来回答这个问题。虽然这个假设并不现实,因为空气的阻力会使石头的下落速度变慢,但是物理学家认为空气阻力对大理石下落的速度影响非常小以至于可以忽略不计。同样的,经济学家也可以做出类似的假设,不仅是因为假设可以使复杂的世界简单化,同时可以使解释这个世界变得更加容易。比如,我们可以假设这个世界只有两个国家组成,每个国家都只生产两种产品,虽然现实中并不是这样,但是通过这种假设,我们可以更加集中的思考问题的实质。

科学思考的艺术,无论是在什么学科中,就是决定作出什么样的假设。比如说刚才提到的,如果从10米高空落下的是一片羽毛的话,那么物理学家就会意识到忽略空气阻力的假设是不正确的,毕竟,空气阻力对羽毛的影响要比对石头的影响大的太多。

同样的,经济学家通过不同的假设来回答不同的问题,假设我们想研究政府改变流通中的货币量的时候经济中会出现什么情况。这一分析的重要内容是价格会作出什么样的反应。经济中的许多价格并不是经常变动的,比如说一本杂志的价格可能好几年才会变动一下。在了解了这个事实的基础上,当我们研究政策变动在长短不同时间中的影响时就会做出不同的假设:为了研究政策的短期效应,我们可以假设价格变动并不大,甚至可以做出价格都是固定的假设;然而当研究政策的长期效应的时候,我们可以假设所有的价格都是完全可变的。就像物理学家在羽毛和石头下落时所使用了不同的假设一样。

2008年10月29日星期三

CLR via C#学习笔记(3)--FCL和CTS

FCL也就是Framework Class Library,是.Net Framework的一个重要的组成部分,它是一系列DLL程序集的统称,这些程序集包含了数千个的类型的定义,每个类型都揭示了一些功能。同时MS也发布了很多其他的类库,比如WinFx和DirectX SDK,这些附加的类库提供了更多的类型,也揭示了更多的功能。下面是一些常用的类库:

Web Service: 包含一系列特殊的方法,以方便用户轻松的处理通过Internet发送的基于XML的信息。

Web Form: 提供了一系列基于HTML的应用程序(网站),通常用于查询数据库和调用Web服务,合并和过滤返回的信息,然后通过一个基于HTML的用户界面,在浏览器中来显示那些信息。

Windows Form: 其实就是一系列Windows GUI应用程序,开发人员通过Windows Desktop 提供的更强大的功能来创建应用程序的GUI。Windows Form可以利用控件、菜单以及鼠标/键盘事件,而且可以直接与底层的操作系统交换信息,也可以查询数据库和使用Web服务。

Windows Console Application: 控制台应用程序提供了一种快速、简单的方式来生成一个应用程序,比如编译器、应用程序和工具通常作为控制台应用程序来实现。

Windows Services: .Net Framework提供了生成"服务"应用程序的功能,它们可以使用Windows Service Control Manager来进行控制。

Component Library: .Net Framework可以生成独立的程序集(组件),其中包含的类型可以轻松的集成到前面提到的任意一种类型中。

由于FCL中包含非常多的类型,所以将相关的一系列类型放到一个单独的命名空间中是很有必要的。比如System命名空间包含Object基类型,其他所有类型都是从基类型继承而来的。


CTS是MS制定的一个有关类型的正式的规范,也就是Common Type System。它已被MS提交给ECMA已完成标准化工作。

CTS规范规定:一个类型可以包含零个或者多个成员,先简单的介绍下这些成员:

Field: 它是对象状态一部分的一个数据变量,不同的字段按照其名称和类型加以区分。

Method: 用Structure Programming的说法来说,其实Method相当于一个Function,它针对对象执行一个操作,通常它会改变对象的状态。Method有一个Name,一个Signature和一个或多个Modifier:Signature制定了参数的数量、类型及其顺序;参数的类型;Method是否有返回值,如果有的话还要制定返回值的类型。

Property: C#他老爸Anders从他的Delphi中搞来的一套重要的思想:Property对于调用者(前台使用人员)来说,它看起来就像是一个Field,然而对于具体的实现者(后台开发)来说,它看起来却像是一个或是两个Method。总而言之,Property提供了一种更人性的实现Accessor/Modifier的途径。

Event: 事件在对象以及其他相关对象之间实现了一个通知机制,比如说,利用按钮提供的click事件,可以方便的在按钮被点击之后通知其它对象。

2008年10月27日星期一

CLR via C#学习笔记(2)--IL和JIT编译

前面提到过,托管程序集同时包含元数据和IL,IL是一种和CPU无关的机器语言,IL要比大多数CPU机器语言高级很多,它可以访问和操作对象类型,提供相应的指令用于初始化对象,在对象上调用虚方法,并可以直接操作数组元素,他甚至提供了用于抛出和捕捉异常的指令。可以把IL想像成是一种面向对象的机器语言。

大部分.Net程序员使用C#,C++/CLI或者是VB编程,这些高级语言会被编译器翻译成对应的IL,当然IL也可以用汇编语言来写,微软为此提供了ILAsm和ILDasm工具用于汇编和反汇编IL。

但需要注意的是,高级语言通常只揭示CLR的一个功能子集,然而IL确允许开发人员访问CLR的所有功能。所以,如果选择的语言隐藏了需要的那部分CLR功能,就可以选择IL或者提供这部分功能的语言来编写这部分代码。

接下来说说JIT编译:

首先考虑下面这个简单的C#方法:

static void Main()
{
Console.WriteLine("Hello");
Console.WriteLine("Byebye");
}

这个方法中,Main方法调用了单件类Console,使得CLR分配了一个单独的内部结构,在这个结构中,Console的每一个方法都有一条记录,每条记录均容纳了一个地址,根据这个地址既可以找到方法的实现。当对这个结构进行初始化时,CLR将每条记录都设置成CLR内部包含的一个未文档化的方法,暂时就把这个方法叫做JITCompiler吧。

Main方法首次调用WriteLine的时候会调用JITCompiler方法,JITCompiler方法负责将一个方法的IL代码编译成本地CPU指令,由于IL是即时(In Time)编译的,所以通常将CLR这个组件称为JITer。

JITCompiler被调用的时候,它需要知道要调用哪个方法以及具体是什么类型定义了该方法,然后JITCompiler会在定义程序集的元数据中搜索被调用方法的IL。JITCompiler接下来要验证IL代码,并将IL代码编译成本地的CPU指令,本地CPU指令保存在一个动态分配的内存块中,然后JITCompiler回到CLR为类型创建的内部数据结构,找到与被调用的方法相对应的那一条记录,然后将最初调用它的那个引用替换成内存块(包含刚才编译好的CPU指令)的地址,最后,JITCompiler会跳转到内存块中的代码,这些代码正是WriteLine方法的具体实现,代码返回时,会返回到Main中的代码并继续执行。

然后Main第二次调用WriteLine,由于之前已对WriteLine的代码进行了验证和编译,所以会直接执行内存块中的代码,完全跳过JITCompiler方法。

所以说,同一个方法只有在首次调用的时候才会造成一定的性能损失,之后对该方法的调用都会以本地代码的形式全速运行,因为不需要再次执行验证和编译本地代码。

需要注意的是,CLR的JIT编译器会对本地代码进行优化,生成这些代码需要更长的时间,但会获得更出色的性能。例如C#的编译器中有两个开关:/optimize和/debug,在VS的Debug配置中,Debug配置是/optimize-和/debug-full,而Release的配置是/optimize+和/debug:pdonly。

N多微软神作的作者Jeffrey Richter认为IL最大的优势并不在于它是脱离了底层CPU的一种抽象,而是它所提供的可靠性和安全性。

2008年10月22日星期三

CLR via C#学习笔记(1)--什么是CLR

想想也是惭愧,作为一个自称是.Net平台开发程序员的人,居然没有看过Jeffrey Richter的CLR via C#,估计会被不少程序员笑话吧。

自己的C#入门书籍是Jesse Liberty的Programming C#,那本书我看了得有三遍了吧,呵呵。但那本书只是让我对C#的语法有了一个了解,而直到半年多后的现在,我也仅仅只是在用C#去编程序,而对.Net这个巨大的平台,知道的仅仅是 Understanding .Net 2.0那本书里得到的一个对.Net平台的一个概览,而实质性的东西却知之甚少。怎么说呢,就是知道该怎么做,而不知道为什么这么做。

作为一个打算把.Net作为自己职业方向的程序员,不对.Net平台(而不仅仅只是C#语言)有一个Thoroughly的了解,我认为是远远不够的。怎么也得知其然且知其所以然吧,所以选择了这本据说是.Net程序员必读的Bible一般的CLR via C#,希望以此来提升自己的.Net内功。

既然已经选择了.Net Framework,就有必要来决定使用哪种编程语言了:通常这是个很难的选择,因为不同的语言具有不同的能力。非托管的C/C++可以对系统进行一些非常低级的控制,而Visual Basic可以非常方便的快速生成UI应用程序和控制COM对象和数据库。

公共语言运行库(CLR)这个名称准确的描述了它的作用,它是一个可由多种不同的编程语言来使用的运行库。CLR的特性可由所有面向它的语言来使用。
事实上,在运行的时候,CLR是根本不关心开发人员用哪种语言写的源代码的,也就是说,.Net程序员在挑选编程语言的时候,应该选择最容易表示自己意图的语言。例如,创建一个简单的WinForm应用程序,使用C#或VB要比使用C++方便许多;对于数学或金融应用程序,使用APL语法要比Perl语法表示同样的意图要容易的多。

创建一个.Net程序,首先使用支持CLR的任何语言要编写正确的code,然后使用相应的Compiler来检查语法和分析Source code,Compiler会生成一个Managed module,也就是一个标准的PE32或者是PE32+文件,他们需要CLR才能执行。

本地代码编译器生成的是针对某种特定架构的CPU(例如x86、x64)专用的代码,相反,所有CLR相容的编译器生成的都是Intermediate Language,由于CLR会管理它的执行,IL有时也叫做托管代码。同时编译器还会在每个托管模块中生成元数据,元数据就是一系列特殊的数据表,他们描述了模块中定义的内容,比如类型及其成员。

元数据有很多用途:
1)在编译时,元数据消除了对头和库文件的需求,因为与引用的类型/成员有关的信息都包含在用IL来实现类型/成员的文件中
2)MS Visual Studio通过Intellisense解析元数据,以方便我们编写代码(智能提示)
3)CLR中的代码验证过程使用元数据来保证代码只执行"安全"的操作
4)元数据允许将一个对象的字段序列化到一个内存段中,并将其发送到另一台机器,然后反序列化,在远程机器上重建对象状态
5)元数据允许垃圾收集器跟踪对象的生存期

2008年10月19日星期日

经济学原理学习笔记(3)--经济学十大原理 III

继续之前的笔记,现在来谈一谈整体经济是如何运行的,也就是经济学十大原理的最后三条原理。

原理八:一国的生活水平取决与它生产物品与劳务的能力

记得以前看过一篇文章,文革的时候,我们是这么教育我们的孩子的:中国人民是世界上最幸福的人民,除了我们,世界上还有三分之二的人活在水深火热的资本主义社会中。现在想想这句话,我还真想水深火热一把呢,呵呵,所以就考了一把IELTS。

不去评论这句话的真实性,但世界各国之间的生活水平相距是惊人的,2003年,美国的人均收入约为3.75万美元,墨西哥为8950美元,而中国是3000美元左右。这种收入上的巨大查遍,体现在生活质量的各种衡量指标上,高收入国家比低收入国家拥有更好的医疗保健,更先进的教育,更多的电视机以及更好的营养。

那用什么来解释各国之间的这种巨大差距呢?原因就是:不同国家之间生产率的差别,也就是一个工人一个小时所生产的物品与劳务数量的差别,导致了几乎所有生活水平的差距。在那些单位时间工人能产生大量物品和劳务的国家,大多数人享有高水平的生活,反之则得忍受贫穷的生活。忘了在哪里看到的,美国工人的生活水平大约等价于中国中等家庭收入的10倍,这就是由生产率所决定的。

原理九:当政府发行了过多的货币时,物价上升

相信看过九年义务教学历史课本的同学应该都还记得,在那本中国近代史上册里,有一副图片,描绘着一个人,骑着二八自行车,背着一大麻袋钞票(似乎叫做法币,呵呵)去购物的场景,这也是我第一次接触通货膨胀(Inflation)这个概念。

现在这个概念应该大家都耳熟能详了吧,什么,你不知道什么叫通货膨胀?呵呵,想想猪肉的价格就可以了。

通货膨胀其实就是经济中物价总水平的上升,由于高通货膨胀会让社会付出很大的代价,因此世界各国的政府都把保持低通货膨胀作为经济政策的一个目标。

大多数通货膨胀的罪魁祸首,就是货币量的增长。当一国政府发行了大量的本国货币时,货币的价值也就相应的下降了。刚开始我举的法币的那个例子就印证了这一点:国民政府发行了过多的货币,导致货币飞涨。

原理十:社会面临通货膨胀与失业之间的短期权衡取舍

虽然从长期来看,货币量增加导致了物价水平的升高,但是短期中的问题就复杂多了,多数经济学家是这样描述货币注入的短期效应的:

1)经济中货币量增加刺激了整个支出水平,从而刺激了物品与劳务的需求。

2)随着时间的推移,高需求导致企业提高物价,受高利润驱动,它会鼓励工人增加他们生产的物品和劳务量,而且会更多的雇佣工人以提高生产量。

3)雇佣更多的工人意味着更少的失业

这也就是在整个经济范围内的最终的权衡取舍:通货膨胀和失业之间的短期权衡取舍。

这种短期的权衡形成了一个周期性的循环。经济周期(Business cycle)就是用生产的物品与劳务量或雇佣的人数来衡量的,经济活动中无规律的、大部分无法预测的波动。

2008年10月15日星期三

计算机组成学习笔记(4)--指令的执行

之前一直在将计算机的一些分类和简史,现在开始具体的关于计算机组成的内容,先聊一聊CPU吧。

关于CPU干什么是什么之类的废话就不多说了,直接进入CPU的组成。

CPU由相对独立的几个部分组成。控制器负责从主存储器中取指令和确定指令类型;算术逻辑部件(ALU)通过完成诸如加法、逻辑或与非等运算来执行指令。同时CPU内部也有一些小容量高速度的存储器,它们用于存放中间结果和一些控制信息,它们由寄存器组成,一般每个计算器的容量都相等,可以存放一个不超过其存储范围的数。

需要提到的一个寄存器是程序计数器(Program Counter),之所以提到它,不仅是因为它的重要性,而且是由于它经常被人误解,实际上它并没有任何计数的功能,它的作用是指向下一条要被执行的指令。另外,指令寄存器(Insturction Register)也十分重要。

下面来说说指令执行的过程:

ALU对输入数据进行简单的计算,然后把结果送入到输出寄存器,经输出寄存器存回到某个寄存器中,当然也可以从寄存器写入到内存中。一般的指令有两种类型:寄存器-内存指令和寄存器-寄存器指令,很好理解的。另外,ALU将两个操作数进行计算并将结果存回的过程称之为数据通路周期,是大多数CPU的核心,从某种意义上,它决定了计算机的性能。

然后具体说下指令执行的步骤:

1)从内存中取下一条指令到指令寄存器中--->2)将程序计数器指向后面的一条指令--->3)判断刚刚取到的指令的类型--->4)如果该指令用到了某个内存单元,则对该内存单元进行寻址--->5)必要的时候,从内存中取一个字到CPU的寄存器中--->6)执行指令--->7)返回第1步准备执行下一条指令

也许用代码来表示会更清晰一些:

public class Interp
{
private static int PC;//Program Counter 程序计数器
private static int AC;//Arithmetic Counter 累加器
private static int instr;//存放当前指令的寄存器
private static int instrType;//指令的类型
private static int dataLoc;//数据地址,若没有则为-1
private static int data;//存放当前数据数
private static bool runBit = true;//运行指示位,需停机时将其关闭

public static void Interpret(int[] memory, int startAddress)
{
PC = startAddress;
while (runBit)
{
instr = memory[PC];
PC++;
instrType = GetInstrType(instr);
dataLoc = FindData(instr, instrType);
if (dataLoc>=0)
{
data = memory[dataLoc];
}
Execute(instrType, data);
}
}
private static int GetInstrType(int instr) { ... } //得到指令的类型
private static int FindData(int instr, int instrType) { ... }//获得数据的地址,若没有则返回-1
private static void Execute(int instrType, int data) {... }//执行指令
}

2008年10月12日星期日

经济学原理学习笔记(2)--经济学十大原理 II

上回提到四个关于人们如何作出决策的原理,这次来聊聊人们之间是如何进行交易的。

原理五:贸易可以使每个人的状况更好

当前世界,大到国家与国家之间的竞争,小到个人与个人之间的竞争,导致很多人都有这样的误区:就是竞争一定会产生利益受到损害的一方,其实并不是这样的。

为了说明这一点,先考虑一下贸易是如何影响你的家庭吧,当你的家庭的某个成员在找工作时,不可避免的,他会要与其他的家庭的成员竞争,因此从某种意义上来说,经济中每个家庭都与其他的家庭存在着竞争的关系。

然而尽管存在这种竞争,但把你的家庭和其他家庭隔绝开来并不会大家变得更好,因为真的隔绝开来的话,每个家庭就不能从事他们所擅长的工作来获取利益。换句话说吧,比如说我自己,是一个程序员,虽然我不会做菜不会缝衣服,但是我可以通过我编程序得到的收入来获得衣食住行这些必需品。但如果把我同他人相隔绝开来,我就得自己做很多自己并不擅长的事情,这大大降低了我的生活效率。

也就是说,贸易是每个人都可以专门从事自己最擅长的活动,通过与其他人的交易,人们可以以较低的成本获得各式各样的物品与劳务。


原理六:市场通常是组织经济活动的一种好方法

20世纪80年代苏联和东欧的解体宣告了计划经济的失败,事实证明,通过中央计划者来决定生产什么物品和劳务,生产多少,以及由谁来生产和消费这些物品和劳务这种政策是错误的。

现在全球绝大多数国家都在努力发展市场经济,在一个市场经济(Market Economy)的条件下,中央计划者的决策被成千上万的企业和家庭的决策所取代,企业决定雇佣谁和生产什么,家庭决定为哪家企业工作,以及用自己的收入来购买什么。这些企业和家庭在市场上相互交易,价格和利己,引导着他们的决策。

伟大的经济学家Adam Smith在他的神作《国富论》中曾经提到过个全部经济学中最有名的观察成果:家庭和企业在市场上相互交易,它们仿佛被一只看不见的手所致应,并导致了合意的市场结果。(其实价格,就是这支看不见的手用来指引经济活动的工具)

计划经济之所以失败,因为当政府去阻止价格根据供求的自发的调整时,它就限制了这只看不见的手对组成经济的千百万家庭和企业之间进行协调的能力。这也就解释了为什么税收会对资源的配置有着不利的影响。

总之,这个观点可能与我们小时所受的教育理念相悖:当一个人去追求自己的利益时,往往使他能比在真正出于本意的情况下更加有效的促进社会的利益。

原理七:政府有的时候可以改善市场结果

虽然市场这只看不见的手拥有这么强大的力量,但不要忘记这一条,只有在个人和企业的产权(Property Rights)得到保护,市场才能正常的运行。打个比方,如果一个农民可以预见到他的谷物会被偷走,那他绝不会去主动种庄稼;如果多数顾客都选择到网上下载免费的MP3或者是购买盗版的CD,唱片公司不会去生产CD。这也解释了为什么很多中国的软件公司的产品并不在中国销售,我就不多说了,呵呵。

因此市场需要政府来进行一定的调节,通常来说,政府干预经济并改变人们根据自己的利益选择的资源配置的原因有两类:促进效率和促进平等,也就是说,大多数政策的目标是既要把经济蛋糕做大,同时也要使这块蛋糕的分割方式尽可能的合理。

计算机组成学习笔记(3)--机型和芯片

根据荷兰老爷子Tanenbaum的定义,他把计算机分成了如下的几类:

一次性计算机:分类的最低端。比如说以前买的那种贺卡,打开就有音乐放出,它所使用的芯片就可以说是一次性计算机的一个范例。当然它的应用不仅有这么简单,现在很热门的RFID(无线射频识别),就是它的一种应用。

微控制器:可以被认为是一台很小但是却很完整的计算机。常常用于不同种类的设备中,比如说家用电器、通信设备等。

游戏机:PS3、Wii、XBOX360….

个人计算机:PC或者Macintosh之流,日常生活中接触最多的计算机

服务器、工作站和大型机:它们的处理能力更强,有一个或多个处理器,就不加赘述了。

接下来就是芯片了,很不幸,AMD的芯片并没有被Tanenbaum所看中。

首先要说的自然是芯片老大Intel的东东了

从最初的4004到第一片16位微处理器8086,在到后来的80486,由于80486这个商标已被人注册,于是Intel就为80586采用了一个别名Pentium,(其实Pentium就是希腊字5的英文写法,玩电脑早一点的朋友都知道,97年、98年那一会,还都管Pentium叫586,管Pentium II叫686呢,呵呵),随着MMX(MultiMedia eXtension)和SSE(Streaming SIMD Extensions)的引入,再到后来Pentium的马甲版本Celeron和Xeon,然后到支持超线程(就是允许程序把它们的任务分解成两个控制线程)的Pentium 4,但随着CPU时钟的飞速提高,发出的热量也变得越来越巨大,3.6G的Pentium4已经有了115w的功率,热量相当于一个100w的白炽灯泡。后来Intel就开始了多核技术的研究,这是后话了~

然后说说UltraSPARC

说到UltraSPARC就不得不提起SUN,SUN可不是太阳能公司~~而是Stanford University Network的abbreviation,它最初是一个印度阿三和几个技术天才的一家生产UNIX工作站的公司,到后来翅膀长硬了就开始自己做CPU了,SPARC实际上就是可伸缩处理器体系结构的简写。

和其他的公司不一样的是,SUN并没有自己去生产SPARC CPU,而是授予其他的半导体公司生产许可权,期望它们之间的竞争能使产品更加质优价廉,因此SPARC芯片也就五花八门。

SPARC最早就是32位机,主频32MHz,到95年的完全64位体系结构的Ultra SPARC I,到接下来的II、III,以及封装了两个Ultra SPARC III芯片的Ultra SPARC IV

最后提一下8051

现在还模糊的记得大二时上的计算机组成课,老师拿了一台很高深的机器,一顿比划,那似乎就是传说中的8051,直到看Tanenbaum的书之前,我还不知道为什么讲解CPU要拿那个老掉牙的东西来讲。

用一个具有20多年历史的8位芯片来讲解计算机组成实在是很eccentric,但是要知道,知道2001年,8为微控制器的年销量才超过4位的微控制器,而现在,8位微控制器销量超过了其他微控制器销量的总和~~ 而且8051的问世时间很长,积累了大量的技术资料和软件,学习起来非常方便。


8051属于MCS-51系列芯片,有兴趣的同学可以Google一下看看。

2008年10月10日星期五

微观经济学原理学习笔记(1)--经济学十大原理I

还记得在考IELTS的前一天晚上时,和班里的一位天才同学在大工本部的旅馆里练习英语口语,当时就聊到了Economics,这时他提到了一个人名Mankiw和一本书Principles of Economics。根据他的描述,这本书是经济学入门的绝佳教材,通俗易懂,言简意骸。

这个同学的眼光我还是很相信的,况且一直想找个机会学习下经济学,于是在IELTS结束后的那几天,上了趟当当,搞定了这一套书,一共三本,宏观经济学、微观经济学和学习指南。

之前断断续续的看了前面的几章,现在打算再重新来一遍,彻底的先把微观经济学这本分册阅读一遍,呵呵,算是养成一种经济的头脑吧。

不像其他的书废话套话那么多,Mankiw的这本书第一章即是全书的精华所在,也就是经济学的十大原理。

首先要意识到,我们离共产主义还有一段日子呢,呵呵。所以要了解稀缺性(Scarcity)这个概念:社会拥有的财富资源是有限的,因此并不能生产人们所希望拥有的所有物品和劳务。而经济学(Economics)呢,就是在这个稀缺的前提下,来研究社会如何管理自己的稀缺资源。同时要意识到,社会的资源并不是有一个全权的独裁者来配置,而是由千百万家庭和企业的共同行动来配置的。

原理一:人们都会面临权衡取舍。

要知道天下并没有免费的午餐,为了得到某一样东西,通常就不得不放弃另外一样东西,也就是说,我们需要在一个目标和另外一个目标之间作出取舍。

社会经常面临的取舍是在效率和平等之间,效率(Efficiency)是指社会能从其稀缺资源中获得的最大利益,而平等(Equity)则是指将这些资源的成果公平的分配给社会成员。换句话说吧,效率就是指经济蛋糕的大小,而平等就是指如何去分割这块蛋糕。政府经常需要在这两者之前做出取舍:比如说,当政府把富人的收入再分配给穷人时,实际上等于降低了对辛勤工作的奖励,结果就是,人们对工作的热情减小了,生产的物品和劳务也就变少了。

原理二:某种东西的成本是为了得到它所放弃的东西

人们往往会面临各式各样的权衡取舍,因此做出决策时就要比较这些方案的成本和利益,但多数情况下,某种行动的成本不是一下子就能看出来的。

比如说,是否上大学的决策。这个决策的利益是丰富了知识,而且会拥有更好的工作机会。但也要考虑到成本,这里的成本并不是大学生活中学费、书费、伙食费等费用的总和,因为这些总和并不能真正代表上一年大学所放弃的东西。

上大学的最大的成本不是在于那些费用,而是时间。当你把这四年的时间用于听课时,很自然的你就不能用这些时间去工作,对多数学生来说,为了上大学而不得不放弃的工资是他们受教育的最大单项成本。至于大学中的伙食费和住宿费,即使你在大学以外的地方,你仍然需要吃穿住行,实际上大学的住宿费和伙食费往往比你单独生活时的费用低很多,这时,节省下来的那部分住宿费与伙食费反倒成了上大学的利益。

同时在这里给出一个非常重要的概念:一种东西的机会成本(Opportunity cost)即是为了得到这件东西而放弃的东西。

原理三:理性人考虑边际变量

在经济学中,往往把人假设成是非常理性的。如果机会成本既定,理性人(Rational people)系统有目的的做可以达到其目的的最好的事情。

现实生活中的很多决策并不是那种黑与白的抉择,而往往是介于其之间。打个比方,当考试临近时,你不会在放弃考试和一天学习24个小时做选择,而是是否多花一小时来复习功课。经济学家用边际变动(Marginal change)来描述对现有行动计划的微小增量调整。理性人往往会通过比较边际利益(Marginal benefit)与边际成本(Marginal cost)来作出决策。

有没有想过,为什么水这么便宜,而钻石确那么贵呢?毕竟人们需要水来维持生命,而钻石并不是不可或缺的。原因就是:人对任何一种物品的支付愿望都基于其边际利益,即物品产生的额外利益,反过来说,边际利益又取决于一个人拥有多少件这样的物品。尽管水是不可缺少的,但是由于水太多了,因而增加一杯水的边际利益微不足道,相反的,钻石就太少了,人们认为增加一块钻石的边际利益是非常大的。也就是说,当且仅当一种行为的边际利益大于边际成本时,一个理性人才会采取这种行为。

原理四:人们会对激励做出反应

大家都知道乘车要系上安全带,但如果告诉你系安全带不但不会降低死亡率,反而会提高死亡率,你相信吗?

激励(incentive)是引起一个人做出某种行为的某种东西,因为理性人通过比较成本和利益作出决策,因此他们会对激励产生反应。

激励在分析市场是如何运行的时候显得至关重要。比如,当苹果的价格上涨时,人们会决定多吃梨而少吃苹果,因为购买苹果的成本增加了;同时,苹果园的主人决定多雇佣工人去种植苹果,因为出售苹果的利益增加了。

以此类推,当安全带系上的时候,会对安全产生什么样的影响呢?直接的影响显而易见,系上安全带后的重大车祸发生时的存活率提高了,但是这并不代表事情结束了,因为系上安全带后,司机的胆子也就变大了… 也就是说,系上安全带的行为对司机开车的速度和谨慎程度产生了影响,(这并不奇怪,下雪的时候,司机开车都会比平时更慢更小心的)这样对行人的安全显然有着很消极的影响。

这个例子告诉我们,当分析任何一种政策的时候,不但要去考虑它的直接影响,同时也要考虑由于激励产生的并不是很明显的间接影响。要记住,如果政策改变了激励,就会使人们改变自己的行为。

2008年10月9日星期四

数据结构学习笔记(2)--算法的威力II

继续来研究最大子序列和这个问题。

经过改良,逐层遍历这种方法的复杂度被从N3降到了N2的级别,可以说是一个非常大的进步了,还有没有更好的办法呢?

这时候该回顾下递归(Recursion)了,先举一个最简单的递归的例子,求一个整数的N次方:

大家都知道要求一个数的N次方的话,先得求出这个数的N-1次方… 以此类推,也就是从这个数的0次方开始,而下面的这段代码,正是用到了这种思想

下面这段代码只能处理幂是正整数的情况
public static int GetPow(int radix,int power)
{
if(power == 0)
{
return 1;
}
else
{
return radix * GetPow(radix, power - 1);
}
}

然后回到最大子序列上,其实仔细想一想,如果把这N个数从中间的那个数分开的话,那么具有最大子序列和的这个序列所在的位置,只有三种可能:左边、右边或者是中间。运用刚才的递归思想,不难编写出下面的Pseudocode:

Function Int PseudoGetMaxSubsequenceSum(Array array)
{
Array arrayLeft=Left(array);
Array arrayRight=Right(array);
Int leftMaxSum=PseudoGetMaxSubsequenceSum(arrayLeft);
Int rightMaxSum=PseudoGetMaxSubsequenceSum(arrayRight);
Int midMaxSum=GetMidMaxSum(array);
return MaxOfThree(leftMaxSum,rightMaxSum,midMaxSum);
}

然后逐步细化,将Left(),Right(),GetMidMaxSum()写成实际的方法,于是就有了下面的代码:

public static int RecursiveMaxSubsequenceSum(int[] arrNumbers, int left, int right)
{
if (left == right)
{
if (arrNumbers[left] > 0)
{
return arrNumbers[left];
}
else
{
return 0;
}
}
int center = (left + right) / 2;
//Calculate the max subsequence sum in the left part
int maxLeftSum = RecursiveMaxSubsequenceSum(arrNumbers, left, center);
//Calculate the max subsequence sum in the right part
int maxRightSum = RecursiveMaxSubsequenceSum(arrNumbers, center + 1, right);
//Equal to MidMaxSubsequenceSum
//Firstly, calculate the max subsequence sum from the center element of the array to the left edge
int maxLeftBorderSum = 0;
int leftBorderSum = 0;
for (int i = center; i >= left; i--)
{
leftBorderSum += arrNumbers[i];
if (leftBorderSum > maxLeftBorderSum)
{
maxLeftBorderSum = leftBorderSum;
}
}
//Secondly, calculate the max subsequence sum from the center element of the array to the right edge
int maxRightBorderSum = 0;
int rightBorderSum = 0;
for (int i = center + 1; i <= right; i++)
{
rightBorderSum += arrNumbers[i];
if (rightBorderSum > maxRightBorderSum)
{
maxRightBorderSum = rightBorderSum;
}
}

int maxMidSum = maxLeftBorderSum + maxRightBorderSum;

//Equal to MaxOfThree
return MaxOfThree(maxLeftSum, maxMidSum, maxRightSum);
}

public static int MaxOfThree(int a, int b, int c)
{
int temp;
if (a>b)
{
temp = a;
a = b;
b = temp;
}
if (a>c)
{
temp = a;
a = c;
c = temp;
}
if (b>c)
{
temp = b;
b = c;
c = temp;
}
return c;
}

根据书上的说法,采用递归的做法可将复杂度降低至N*logN的地步(这里就不给出具体的证明过程了)

接下来就是这个最令人拍案叫绝的算法了

public static int LinearMaxSubsequenceSum(int[] arrNumbers)
{
int maxSum = 0;
int tempSum = 0;
for (int i = 0; i <>
{
tempSum += arrNumbers[i];
if (tempSum>maxSum)
{
maxSum = tempSum;
}
else if (tempSum<0)
{
tempSum = 0;
}
}
return maxSum;
}

只用了一轮循环即完成运算,复杂度为N,原理请大家自己思考。

2008年10月8日星期三

计算机组成学习笔记 (2)--分层架构


就像之前提到的,计算机所能直接理解的语言L0通常都是很难理解的(比如说机器语言),为了简化大部分程序员的工作,开发一种基于L0之上的且易于理解的语言L1就显得很有必要了。同时,也需要一个高效的将L1程序转化成L0指令序列的解释器或者是翻译器M1(其实它起的就是虚拟机的作用)

然而,为了让L0到L1的转化过程变的可行,L1和L0的差别并不能"特别"大,这也就意味着,虽然L1要比L0易于理解一些,但对于一些人仍然显得不够理想(比如说从机器语言到汇编语言,虽然汇编要比二进制码可读性强了很多,但汇编的难度还是不低),于是,在L1的基础上,又有了相应的语言L2和对应的虚拟机M2… 直到语言Ln和对应的虚拟机Mn。

按照这种层次,逐层抽象,在虚拟机Mn上编写程序的程序员并不需要考虑下层的翻译器或者解释器,程序由这种逐层抽象的结构保证其正确性,而无需了解编译器或者解释器实现的具体细节。从而获得正确的结果。

Tanenbaum是这样描绘计算机的分层结构的:

从上至下依次为:

面向问题语言层:为编写解决现实问题的应用程序提供便利,例如 Java C/C++之类的高级语言

汇编语言层:实际上是底层语言的符号表示,只是为程序员提供了一种更舒服的编写底层程序的途径

然后是:操作系统机器层---->指令系统层---->微体系结构层,这些层并不是为普通的程序员提供的,而是为支持高层所需的翻译器或者是编译器而设计的

最后一层是数字逻辑层,这一层涉及的更多的是电气知识。

每层的数据类型、操作和特性构成了该层的体系结构,它的主要作用就是解决该层用户所能看见的问题。

2008年10月7日星期二

计算机组成学习笔记(1)--翻译与解释

终于开始恶补CS的基础课程----Computer Architecture,这门以前深恶痛绝的课程。没办法,作为一个SE的学生,对于硬件总是很反感的。

自己使用的Textbook是Tanenbaum的那本计算机组成 结构化方法第五版,自从看过那本Computer Networks第四版之后,就对这个荷兰老头子的书产生了浓厚的兴趣,头一次见到写的这样风趣而且不乏深度的教材,让我头一次看一本计算机的书看的不可自拔。

本以为Chapter One只是一些简要的介绍,没想到第一页就解决了我以前一直不明白的一个问题:Differences between Compilation and Interpretation~

简单的说吧,假设有一台计算机,它只支持语言L0进行编程操作,然而L0却很难,一般的人根本搞不懂,于是便有高人发明了语言L1,它抽象了L0的许多功能,而且比L0更加简单易用,但这时候问题来了,计算机只认识L0不认识L1,这怎么办呢?

于是便碰到了一个语言转化的问题,也就是如何将高易用性的L1转化成计算机认识的L0呢?

这个转化过程,就扯到了翻译和解释这两个概念:

假设现在有一个用L1编写的程序PL1

一种方法是把用PL1翻译(Compile)成一个等价的L0指令序列,计算机通过执行这个L0指令序列,来完成原来的那个PL1的功能,这个过程叫做翻译。

另外一种方法,就是用L0写一个程序(也就是解释器 Interpreter),把用PL1输入数据,然后一边顺序检查PL1的每条指令,一边顺序直接执行等效的L0指令序列,从而完成PL1的功能,这个过程就叫做解释(Interpretation)。

翻译和解释其实都是通过执行PL1所对应的L0指令序列来实现的,其中的区别在于,翻译的时候需要先把整个PL1转化成L0指令序列,然后执行这个L0指令序列,而解释的时候,PL1中的每条L1指令在被检查和译码后会立即执行。因此,解释的速度一般要比翻译快,然而,解释的实现也要比翻译难。

(关于编译的原理,这是一门很复杂的学科,建议参考龙书的第二版和一些关于自动机的书)

数据结构学习笔记(1)--算法的威力I

自从出国的计划流产之后,决定利用现在这段实习的时间,重温一遍计算机基础学科,也就是数据结构、计算机组成、操作系统、数据库、编译原理和计算机网络,就按照这个顺序来慢慢的走了。

之前已经把M.A. Weiss的那本 Data Structure and Algorithm Analysis in C 2nd Edition看过了一遍,作为一本数据结构的本科教材。在讲解的细致程度和深入程度上,它绝对达到了相当的高度。作为一本讨论计算机算法的书,它也是经典中的经典。所以这次温习数据结构,用的教材还是它,而那本CLRS则是当成一本COOKBOOK来使用了,看C的代码还是要比看Pseudocode要舒服不少的。

从摩尔定律提出到现在,30多年过去了,计算机的性能以几何的速度增长,如果按照这个速度下去,没准有一天算法会丧失它的意义呢,呵呵。不过估计那一天还早呢,毕竟,有快的算法为什么还要用慢的呢,如果说解决同一个问题,一个方法要用不到一秒,另一个方法得耗个十年八年的,你会选择哪个方法呢?

这本书的开篇就提到了一些经典的问题例如最大数选择问题和最大子序列求和,这两个问题也是不少IT公司的常见笔试问题,解决方法有不少,不过真正的最优解法(线性时间内解决)就只有那一种。虽然这本书是1997年就有,2002年就有中文版了,但到现在还是没几个人知道它的解法,不得不感叹下如今国内IT人士基础的匮乏。

现在就拿这个最大子序列求和问题开始我的数据结构复习之路吧,呵呵。

问题很简单,就是给出N个整数,正的负的都有(正的是一定有的),求出其中连续的子序列的最大值。

例如:8,9,-10,12的结果是19;9,-10,3,2,-1,7,-6的结果就是11

一般人想到的解法是逐层遍历,也就是说把这N个整数所有的连续子序列的和都计算出来,然后取最大的那个,就是答案了,这样的程序并不难写:

实现的C#代码如下:

public static int ExhaustiveMaxSubsequenceSum(int[] arrNumbers)
{
int tempSum;
int maxSum = 0;
int count = arrNumbers.Length;
for (int i = 0; i < j =" i;" tempsum =" 0;" k =" i;">maxSum)
{
maxSum = tempSum;
}
}
}
return maxSum;
}

N个数的子序列有1*2/2+2*3/2+…+N*(N+1)/2种,也就是(N3+3N2+2N)/6中,复杂度为N3

然而仔细研究代码之后,发现最内层和次内层的循环存在冗余,可以合并,改良后的代码如下:

public static int ImprovedExhaustiveMaxSubsequenceSum(int[] arrNumbers)
{
int tempSum;
int maxSum = 0;
int count = arrNumbers.Length;
for (int i = 0; i < tempsum =" 0;" j =" i;">maxSum)
{
maxSum = tempSum;
}
}
}
return maxSum;
}

此时使用了两层循环,算法的复杂度为N2

还有没有解决的方法呢?是时候使用下递归了。

2008年10月5日星期日

制作好了计划表

现在一共有13门课程要学习,目前已经圈定了7门。

出国暂时是不可以了,怎么也得等两年,所以得做好再考一次IELTS甚至是TOEFL或者GRE的准备了。

选定的课程有:

Intermediate English
Elementary Japanese
Elementary Mathematics
Principles of Microeconomics
Fundamentals of .Net Platform
Fundamentals of Data Structure
Fundamentals of Computer Architecture

英语是必须要学的,必须得不断提高的,这个无可替代

在大连这个地方,也许以后得用到日语,所以还是保持一下自己的那点日语水平好了(二级不敢想,三级吧)

发现数学这玩意很有趣呢,感觉自己对数学有点生疏了,简单的复习一下吧

经济学原理是作为一个要独立生活的人所必须的常识

.Net是目前正在用的技术,有必要熟悉下这个平台而不是只是拿C Sharp编来编去的

数据结构那本书以前已经看过一遍了,这么重要的课程还是再看一遍吧

搞到了Tanenbaum的计算机体系结构第五版,大二时这门课学的一塌糊涂,作为一个ITer和CSer,有必要了解一些必要的计算机硬件架构知识

2008年9月8日星期一

最近要读的书的清单

暂时分为这几类:

近期需要看完的书:CLR via C#第二版,.Net设计规范,设计模式解析第二版(配合Head First设计模式)

近期需要看的书:代码大全第二版,经济学原理第四版,

长期需要看的书:什么是数学,IELTS词汇21天,标准日本语初级上册

2008年6月25日星期三

转载的一篇自己认为不错的文章~

给计算机系学生的建议
作者: 周思博 (Joel Spolsky)
译: Chen Bin
2005年1月2日

虽然大概一两年前我还在夸夸其谈桌面应用程序是将来的潮流,大学生们现在还是偶尔向我请教职业发展的问题。所以我把我的建议写下来。以供学生们阅读,嘲笑,忽略。
大多数锐气十足的学生从来不向前辈征求意见。在计算机科学领域,这样做是正确的。因为前辈们很可能说些“在2010年前,市场对于那些只会敲击键盘的代码工人的需求将会超过一亿(因此前景是乐观的)”,或者诸如“Lisp语言现在真的很热门”。
我和那些前辈也差不多,当我给别人建议时,实际上我不知道自己在说些什么。我是如此的落后于时尚,以至于连AIM也搞不明白,而不得不使用 email(恐龙时代的产品,在那个时代,音乐是刻在扁扁的的圆圆的盒子上,噢,那种盒子叫cd)。(译者按:我认为祖儿这里在说反话,后文很多地方作者 都在说反话,读者尽量理解这种美国式幽默吧。)

所以你最好不要理睬我将要说的,你应该立刻去制作某种在线交友软件。
然而,
如果你喜欢编程,那就感谢上帝吧:你属于幸运的少数人,这些人喜欢工作,他们的工作可以保证他们能过上舒适的生活。大多数人没有这么幸运。对大多数 人来说,工作是不愉快的,忍受工作的目的攒钱,是为了在年满65岁退休后能过上自己想过的生活。如果他们想过的生活不需要灵活的膝盖,明亮的眼镜,轻盈的 脚步的话。
现在让我回到主题,我将提供一些建议。
好了,不罗嗦了,下面就是Joel给计算机系学生们七条免费的建议:
1. 毕业前学会写作
2. 毕业前学会C语言
3. 毕业前学习微观经济学(microeconomics)
4. 不要因为某些非计算机课程枯燥无趣就敬而远之
5. 学习有大量编程实践的课程
6. 不要担心工作都跑到印度去了
7. 好好做夏季毕业实习
让我逐条解释这些建议。但解释之前我要说明一下,如果因为这些建议是Joel的建议你就打算无条件地接受,以至于连我的理由都想跳过,那么你就太单 纯,太容易被别人骗了。如果你是那种单纯的人,我还要给你第八条建议,找心理医生咨询一下如何培养自信(self-esteem)。
毕业前学会写作
如果Linus Torvalds不懂如何布道的话,Linux会成功吗? 正象每一个黑客,Linus精通写作,他知道如何准确地在email和邮件讨论组中使用书面英语表达自己的思想,所以他能够从全世界召集大量志愿者为Linux工作。
你听说过最近风靡全世界的极限编程(Extreme Programming)吗? 即使你不懂什么是极限编程,你至少听说过这个词。为什么?因为宣传极限编程的人都是天才的作者和演说家。
就看看你身边的那些小型的软件开发组织吧,最有权力和影响力的人是那些可以用自信,准确,舒适的英语交流的人。好吧,我承认这些人也许言过其实,但是你无可奈何。

一个合格的程序员和一个伟大的程序员的区别不在于知道多少种编程语言,不在于他们是喜欢Python或者Java,而是在于他们是否擅长表达。他们能够说服,所以他们获得权力。他们能够写清楚明白的评论和接口文档,所以他们使得别人不用重写, 而可以重用他们的代码,否则他们的代码就是毫无用处的。他们也能够写出清晰的用户手册,于是最终用户可以理解他们的代码是做什么用的,明白了他们的工作的 价值。sourceforge埋葬着许多精美的代码,这些已死的代码无人使用,因为代码的作者很少写(或者根本不写)用户手册。
我不会雇佣一个不懂写作的程序员。如果你擅长写,你就很容易找到工作,紧接着你就会被要求写技术规格文档,这意味着你已经被管理层注意到了。
大学里有一些课程,要求你做很多的写作练习,不要犹豫,赶快参加这些课程。不要错过任何要求你每周或者每天练习写作的课程。
给自己建立一个网络日志(weblog)。在上面写的越多,你会写地越容易。写地越容易,你就写地越多,这是一个正向地循环激励。
毕业前学会C语言
我可没有说是C++。虽然现在用C的工作不多,但是掌握各种编程语言的程序员事实上用C来交流(lingua franca);更重要的是,C比某些“现代”语言更接近机器语言。我不管现在大学里在教什么流行的垃圾语言(trendy junk),你至少得花一个学期接近机器。否则,你不可能使用高级语言写出高效的代码。这意味这你不会有机会写编译器或者操作系统,也许这是更好的编程工作;别人不会相信你能够为大项目设计架构。无论你知道多少高级的控制结构,知道如何进行错误处理,如果你不能解释为什么while (*s++ = *t++);的 意思是进行字符串拷贝(而且对你而言这是世界上最自然,最易懂的代码),那么你就是在对编程一窍不通的状态下编程(programming based on superstition)。打个比方,就好比一个医生不懂基本的解剖学就给你开处方,如果你问这个医生为什么开这种药而不是那种药,他会说因为医药销售 代表说这种药有用。
毕业前学习微观经济学(microeconomics)

我个人对经济学的一些理解:在经济学刚诞生的时候,它只是局限于有限的领域,在这些领域中人们发展和发现了很多有用的理论和很有趣的事实,这些理论和事实是从逻辑上是可以证明的。然后, 经济学开始走下坡路了。 “有限的领域”就是微观经济学,它对于商业可以进行有意义的指导。然后,事情就开始变糟了(以下部分你可以跳过),你接下来碰到的是讨论诸如失业率和银行 利率之间关系之类东东的宏观经济学,很多时候宏观经济学讨论的理论都是无法证明正确或者错误的。接下来事态更加恶化了,经济学中的一些领域开始和物理学搭 界,嗯,学习物理经济学也许你帮你在华尔街找到好工作。言归正传,无论如何请学习微观经济学,因为你需要知道什么是“供给和需求”,什么是竞争优势,什么 是净现值(NPVs,Net Present Value,指项目经济寿命期内现金流入总和与现金流出总和之差额),什么是折扣和边际效用(discounting and marginal utility),如果你真想了解商业是如何运作的话。
为什么计算机系的学生要学习经济学?因为理解商业基本规律的程序员对商业界来说是宝贵的程序员。我记得无数个程序员使我非常沮丧,因为他们在代码中 坚持某些疯狂的设计,这些设计从技术上来说,完美;从资本主义的角度来看,发疯。如果你是一个理解商业的程序员,商业会给你回报。这就是你要学习经济学的 原因。
不要因为某些非计算机课程枯燥无趣就敬而远之
首先,你需要让你的学分平均分(GPA)看起来漂亮点。
不要低估学分平均分的威力。很多雇主和人事经理(包括我)阅读简历时首先看成绩,为什么?因为这代表了大部分的教授在很长的时期内对你的学业的一个 平均的看法。托福成绩(美国的托福大致相对于我国的高考中的语文考试)?哈,几个小时的测验而已。当然学分不一定说明了一切,如果你修的是很难的课程,学 分就有可能低一点。即使你的学分平均分很高,我还是要看各科分数是否一致。如果你应聘的是软件工程师职位,我为什么要关心你在大学里学的欧洲历史课程分数的高低呢?毕竟,历史很枯燥。那么要是你要编程的部分也是很枯燥的,你是不是要放弃了?事实上,有时候编程是枯燥的,如果你不能忍受编程中的枯燥部分的话,你就不能完成整个工作,雇主不愿意雇佣你这样的员工。
我在大学里修过一门叫做“文化人类学”的课程,因为那时候我也搞不懂我到底要学什么,听起来这么课程可能还蛮有意思的。

出 乎我的意料。我不得不阅读大量讲述巴西热带雨林中的印第安人如何如何的书,让人真昏昏欲睡。听老师讲解也好不到哪去,我发觉看教室外的草如何长更有趣点。 土著人如何烤蕃薯藤和我有什么关系?我为什么要去讨论如何烤蕃薯藤?但是期中考试马上就要到了,我暗暗下定决心,如果我能跨越“文化人类学”这个障碍,以 后也许没有什么能难倒我了。我决心得A并且得到了A。以后当我不得不坐在林肯中心,连看18个小时的瓦格纳的《尼伯龙根的指环》时,我终于明白我为什么要 学习“文化人类学”了,相比之下,我也能忍受这种歌剧了。
学习有大量编程实践的课程
我还记得决定不去读研究生的那一刻。
就是在学习《动态逻辑》(Dynamic Logic)这门课的时候,我记得是耶鲁的Lenore Zuck(一个天才的教师)教的。
修这门课的时候,我已经不再是雄心勃勃了。我可不指望在这么课程中得个A,我梦想的是混个及格。逻辑本质上是很简单的:如果结论正确,前提必须正确。例如,如果“所有读书好的人都能找到工作”并且“张三的读书好”,那么“张三能够找到好工作”。就这么简单。
但是我要学的是动态逻辑, 动态逻辑和一般逻辑差不多,但是要考虑时间因素。例如,“在你开灯之后,你可以看见你的鞋子”加上“过去灯被打开了”意味着“你现在可以看到你的鞋子”
动态逻辑学对于象Zuck教授这样的天才理论家来说非常诱人,因为这门学科的知识也许可以用来证明计算机程序是否正确。我记得在第一堂课上,为了证明“如果你有一盏关着的灯”并且“你按了一下开关”,那么“现在灯亮了”,Zuck教授就使用了两黑板加上边上的墙壁。
证明过程难以置信的复杂。我觉得如此复杂的证明过程很可能会有小错误,但是我没办法证明证明过程本身是正确的。事实上,写在黑板上的证明跳过了很多中间步骤,许多步的证明使用了演绎法,使用了归纳法,以及一些研究生才懂的证明方法。
作为课后作业,我们需要证明以下命题:如果灯过去是关着的,并且现在它是开着的,请证明有人按了开关
我真的试着证明它了。
我花了许多小时,试图证明这个命题。
在无数个小时的努力后,我发觉Zuck博士的原始的证明有一个逻辑上的错误,也许这个错误是我的笔记抄错了,我不知道。于是我终于认识到,如果为了 证明一个简单的问题需要花三个小时写下几黑板的证明步骤,再考虑到这个漫长的证明过程中可能会引入种种错误,那么这种机制是不可能用来证明任何有趣的东西的。
对动态逻辑学家来说,有用是无关紧要的。
于是我退出了那门课程,发誓永远不会去读计算机系的研究生。
这个故事的主题是,计算机科学和软件开发不一样。如果你非常非常幸运,你的学校会开软件开发的课程。然而,他们也可能不开这样的课程,因为名牌大学认为教授实用技巧的事情应该留给那些二三流的技术学院或者某些释放犯人再安置计划。你可以在任何地方学习编程,我们是耶鲁大学,我们培养未来的世界领导人。你付给耶鲁16万美元的学费就是为了学习如何写循环语句吗?你把耶鲁当成什么地方了?Java速成班吗?哼。
问题在于,我们没有一个专业的学校教授软件开发。所以如果你想成为一个程序员,你可以进计算机系读书(当然计算机科学也值得学习),但是你学的不是软件开发。
如果走运的话,你可以在计算机系发掘出很多有大量编程实践的课程,就象你能在历史系找到很多提供写作水平的课程一样。这些课程绝对值得学习。如果你 喜欢编程,不要为你不能上教授诸如lambda算子或者线性代数的课程沮丧,在那里你连摸一下计算机的机会都没有。找找看有没有名字中带有“实习 (Practicum)”字样的课程,不要在乎Practicum是个拉丁语。有用的(无奈状)课程就是需要在课程名中塞一些拉丁语,才能从那些装模作样 (Liberal Artsy Fartsy)管理层的眼前蒙混过关。
不要担心工作都跑到印度去了
啊哈,如果你人在印度,你就无所谓了。愿你享受外包带来的工作机会并顺祝身体健康。
但是我听说现在愿意读计算机系的学生越来越少了。据说原因之一是工作机会都跑到印度去了。我认为这种观点是大错特错。首先,根据眼前的商业时尚选择 事业是非常愚蠢的。其次,即使工作真的都跑到印度和中国去了,编程对于其他有趣的工作来说都是极好的训练,例如业务流程工程(business process engineering)。第三,无论是在美国还是印度,好程序员仍然是非常短缺的,请相信我。当然,现在有许多所谓搞IT的人吵吵嚷嚷地说就业形势不 好,工作太难找。但是事实如何?恕我直言,好程序员找工作还是很容易。第四,你还有更好的主意吗?历史系的毕业生找工作更容易吗?去法学院如何?据我所 知,99%的律师恨他们的工作,每分钟都恨。而且律师一周工作90小时。正象我以前说过的,如果你喜欢计算机,那么感谢上帝,你将属于全世界人中的极少数 的幸运儿,这些幸运儿热爱他们的工作,而且工作也可以提供体面的收入。
实际上,我也不认为报考计算机系的人越来越少有多大的意义。相对于internet泡沫时期大家都疯狂的往计算机系挤,现在的人数回落只是回归正常 水平而已。在泡沫时期,我们这个行业涌入了许多对计算机毫无兴趣的南郭先生,他们梦想的是拿着高的吓人的薪水加诱人的期权,然后年轻退休。谢天谢地,现在 这些人都跑了。
好好做夏季毕业实习
明智的招聘者知道热爱编程的人初中就为当地的牙医写数据库程序,高中就在计算机夏令营教课,为校报规划网站,在某个软件公司做实习。他们找的就是这样的人。

如 果你喜欢编程,你最容易犯的最大的错误就是“有活就接”。我知道,其他专业的学生假期打工可顾不了这些条条框框。但是你不一样,你拥有一种特殊技能,不要 浪费它。当你毕业时,你的简历上应该已经罗列一堆的编程工作实习。让其他人去“为大家提供租车服务”(Tom Welling是个例外,他业余时间去演超人)。
最后,为了让你的生活更容易一点,也为了说明我这篇文章是能够自圆其说的,我将给我自己的公司的做做广告。我的公司Fog Creek软件公司,可以为大学生提供软件开发方面的实习机会。在我们公司,你可以学习“编码,开发,商业”。去年在我们公司实习的Ben就是这么说的,可不是因为我给他什么好处他才这么说。二月一号截至,抓紧机会吧。
如果你听了我的建议,你就会太早地卖掉Microsoft公司的股票,拒绝Google提供的职位,原因是因为你已经拥有自己的公司了。到时候可别后悔,更别怪我,呵呵。