-
2012-05-20
代码之丑(十一)
全局变量永远是不受欢迎的,因为它会带来太多的问题,所以,诸如Java这样的程序设计语言干脆摒弃了全局变量。一旦我们有机会面对全局变量,想都不要想,干掉它。
if (IDLE == g_status) {
...
}那个g打头的家伙就是全局变量,它就是我们的靶子。第一直觉,我们不要直接访问全局变量,那就用函数把它封装起来:
int getCurrentStatus() {
return gStatus;
}于是,代码变身了:
if (getCurrentStatus() == IDLE) {
...
}把变量封装成函数,从某种角度说,这是一种进步。但我想说,这还不够。这只是一种简单的封装,本质上来说,这与直接暴露数据差别不大,我们需要更好的封装,通常的做法是封装出行为。行为从哪来,从实际需求来。
就以上面这段代码为例,我们封装了status,其实,它的目的是为了与IDLE状态相比较,这就是一种行为,我们可以这样封装:
bool isCurrentStatus(int status) {
return status == g_status;
}if (isCurrentStatus(IDLE)) {
...
}还有一种修改方式,既然IDLE是一个固定的常量,索性把它也隐藏起来:
bool isIdle() {
return IDLE == g_status;
}if (isIdle()) {
...
}实际上,这种封装出行为的方式不仅仅适用于全局变量,把数据拿出来再用的情形也是经常可以见到的:
if (machine.getStatus() == IDLE) {
...
}封装的方式同上面一样,这里选择一种实现:
class Machine {
...
bool isIdle() {
return status == IDLE;
}
}if (machine.isIdle()) {
...
}封装,就得封装出个行为来。
-
2012-05-13
ThoughtWorks校园行,第二步
自从迈出了ThoughtWorks校园行的第一步,西电的校园活动就一次次进行下来,我们讲了如何写代码,讲了完整的开发过程,后续的一系列校园技术讲座也已经排上了日程。
其实,单以效果而言,技术讲座所带来的影响是有限的,对于参加讲座的学生来说,除了知道很多新名词、新做法,多半也就是看个热闹。直接动手实践,才是一个更好的做法。
感谢我们优秀的市场MM,她为我们打开了一片新天地。感谢西安交通大学软件学院的领导具备的卓越眼光,为他们的学生提供了一个接触外面世界的机会。
于是,我们有了一个新的机会,在西安交通大学开设一门软件开发的课程。
这门课,我们称之为“现代软件开发”,其目的就是为了告诉同学们,不同于传统方式的开发方法。其实,我们本可以叫敏捷软件开发方法,但我着实不喜欢这个名字,因为这已不是我追求的东西,我只想把一些好的东西告诉同学们,所以,选了一个不容易过时的名字。
我们是这样设计的这门课,采用上下半场的方式运作。上半场,我们会介绍一些基本的开发方法,比如整洁代码,比如重构,比如自动化等等。而下半场,则完全是实践。我们选取了一个项目,按照我们的方式运作了一个项目,我们的同事会与同学们结对,让他们直接体会最原汁原味的ThoughtWorks开发方式。
就在这个周末,这个系列课程终于迈出了第一步。
第一次课基本上算是一个课程介绍,让为有兴趣选修这门课的同学了解这门课,以及我们的上课方式。我们讲了公司里如何做软件,还演示了结对开发和TDD。
第二步就这样迈了出来,在接下来的一段时间里,虽然要牺牲一些业余时间,但这也是一个很好的尝试,让我们的课程更加系统化,也给了我们的同事一个很好的锻炼机会。
我们的同事在课程的结尾送给同学们一句话,与其周末逛街打DOTA,还不如来写写代码。嗯,就是这样。
-
2012-05-08
五年思想工作者
五年了,真快。
工作十年,我只工作过两家公司:为我打好基础的东软和为我打开视野的ThoughtWorks,每家五年。这个换工作的速度,在IT行业里,应该算是蜗速了。
当年进到ThoughtWorks,我最想解惑的是如何做好软件,因为之前的开发经历里,我有太多想不明白为什么的地方,比如,为什么要写文档。选择加入ThoughtWorks,我知道这里有敏捷,貌似是解惑的途径。
随着自己在ThoughtWorks做过项目的增多,我了解了敏捷,这些曾经的困惑逐渐得以挥去。当有机会尝试咨询,我开始得到从更多角度看待软件开发,我开始相信改变。渐渐地,我知道了我追求的其实不是敏捷,是持续改进。
去年带了一个项目,在那个项目里,我做了一个尝试。我把项目的目标订在了人才培养,而非传统意义的交付。于是,在这个过程中,我花了大量的精力去教新员工写代码,帮他们设定个人成长目标,教他们做事,带着他们吃好玩好……就是这样一个几乎都是由新ThoughtWorker——甚至大多是毕业生——组成的项目组,后来,这个项目成了Michael Chen口中最接近他心目中全面成功的项目。
早在做咨询的最初,我就认识到,敏捷实施,管理实践很快上手,但新鲜劲一过,就会发现,收效甚微,而技术实践实施起来,却难上加难。我一度很悲观地认为,这事没法搞,我再厉害,也不可能让他们把我十年的东西在几个月内学会。去年的这个项目实验让我这个问题有了新的思考。
真正要敏捷起来,我们要做的并不是敏捷实践,而在于提升团队中每个人的能力,而其中的关键在于,营造团队学习氛围。这正是我们在ThoughtWorks西安办公室努力营造的,事实证明,在这样的环境下,整体的成长是很快的,而学习氛围更好的团队其实也是各方面做得更好的团队。
徐昊对这个问题也有属于他的思考,结合了我们在ThoughtWorks西安办公室所做的各种努力,他提出了LOT(Learning Organiation Transformation,学习型组织转型)。
从了解如何好软件,到告诉别人如何做好软件,到思考真正提升团队能力,还好,五年的思想工作者,光阴不算虚度。
-
2012-04-23
构造函数沉思录
缘起
构造函数,是由C++引入主流程序世界的,其用意是在《C++语言的设计与演化》如是表达:
它建立起其它成员函数进行操作的环境基础。
在很早的一篇blog《对象的声明》中,我曾探讨过构造函数的来龙去脉。对于面向对语言而言,构造函数似乎是标配。
一个语言特性,一旦被扔到真实世界,随之而来的是,其使用往往会超出其设计者的初衷,构造函数亦是如此。
事实上,通过前面C++之父的描述,我们依然很难定位构造函数的准确用法。所以,我们常常看到许多人把诸多操作强塞入构造函数,造成构造函数极为复杂,进而关于导致了一些复杂的语法讨论,比如如何处理构造函数抛出的异常。
这里要讨论的是构造函数的另一个常见问题。
重载构造函数
同样是在《C++语言的设计与演化》里,有这样一段描述:
观察发现,允许定义多个构造函数很有价值,因此这也就成了C++重载机制的一个重要应用方面。
是的,我要说的就是构造函数重载。构造函数可以重载,Java和C#也拿了过来,这似乎成了一种约定俗成。
在实际应用中,有不少人会这么做:给一个类创建多个构造函数,有的初始化了全部的字段/成员变量,有的只初始化诸多字段/成员变量中的几个。下面便是一个例子:
public Image(URL url, Tag tag) {
this.url = url;
this.tag = tag;
}public Image(URL url) {
this.url = url;
}这么做的原因通常是,初写这些代码时,这些构造函数要用在不同的场合下。比如,在产品代码中,我们需要的可能是一个完整的对象,而在测试代码中,针对要测试的内容,我们只要设置几个字段即可。
但是,因为它们都是构造函数,名字完全一致,其最初的意图无法体现,后来的人看到这样的函数,图省事,便拿过来用。随后一些初始化不完整的对象就出现在系统中。随着系统的不断演进,残缺的对象在很多情况下就会出问题,于是,为了修补,我们再向代码里添加一些setter。与setter相伴的往往是,可变(mutable)对象的出现。而许多系统的状态不稳定就是由各种可变对象造成(这也是一个值得讨论的话题)。貌似很简单的构造函数蕴含着诸多的问题。
就这个问题而言,可以怎样解决呢?
一种常见的解决方案是,类只提供一个完整的构造函数,至于其它部分,则采用工厂方法完成。比如上面的例子,对Image类,我们只有一个构造函数:
public Image(URL url, Tag tag) {
this.url = url;
this.tag = tag;
}如果在测试里用到,就为它创建一个工厂方法:
class ImageForTestFactory {
public static Image createImageWithURL(URL url) {
return new Image(url, null);
}
}Image的第二个参数Tag,这里就简单的设置为null,事实上,我们可以根据需要进行设置。比如,我们需要所有的字段都不能为空,这里就可以提供一个缺省的Tag。这段代码的使用者根本无需顾及Tag究竟是怎样。
工厂方法很大的一个价值,便在于它提供了名字,表明意图。名字到底有多大价值,如果你对整洁代码(Clean Code)有所追求,便就会发现,关于整洁代码的讨论,第一个要讨论的东西便是命名。
事实上,这些做法并不如何特殊,《Effective Java》第二版,开篇讨论的就是这样的问题。条款1就是“考虑以静态工厂方法代替构造函数”。这个条款里面建议的方案更加激进,建议将构造函数设置为private。这样一来,人们就完全没有机会使用该类的构造函数,只能通过其提供的工厂方法构造对象:
class Image {
private Image(URL url, Tag tag) {
this.url = url;
this.tag = tag;
}public static Image createNewImage(URL url, Tag tag) {
return new Image(url, null);
}...
}另外一种值得考虑的做法是,采用builder模式。《Effective Java》第二版的条款2给出了更详细的解释。所以,如果类里有多于一个的构造函数,那么请考虑其它方式代替。
无构造函数的Go
顺着这个思路,再进一步,我们完全可以写出“除本类之外,没有new本类对象的代码”。换句话说,如果不是语言层面有所限制,我们完全可以抛弃构造函数,而事实上,Go语言就这么做了。
在Go语言里,如果我们要构造一个对象可以这么做:
type Person struct {
name string
}func NewPerson(name string)(*Person) {
p := new(Person)
p.name = name
return p
}在语法上,Go语言本身并没有类,但从C/C++的年代我们就知道,struct和class本质是一样的。所以,实际上,NewPerson就是一个构造函数,它负责初始化了Person的相关字段。构造一个对象的方法就可以这样:
p := NewPerson("dreamhead")
从本质上说,NewPerson就是一个工厂方法。当然,Go语言之所以可以这么做,因为其struct的所有字段都是public,可以自由访问,在面向对象程序设计语言中,恐怕没那么简单。或许,抛开构造函数的做法,让我们一下子回归到了最初的年代,但同之前模糊的印象不同,如今我们对构造的概念有了全新的理解。我们依然要构造对象,只是不再依赖于构造函数而已。
是时候反思一下构造函数了。C++设计于80年代,那时候,设计模式还不是主流,那时候,编写代码更多强调的是功能,而非整洁。
-
2012-04-11
从Go看,语言设计(二)
书接上文,继续从Go看语言设计。
并发编程
从多核CPU进入人们的生活,并发编程就成为编程中的新热点。在许多语言里,并发是由库提供的,而对于Go语言,并发则是语言的一部分。曾经,一说到并发编程,就会让人想到多线程/多进程、共享内存等等。Erlang改变了许多人关于并发编程的认识,基于消息的通信模式如今已逐渐成为新的标配,Go语言选择的也是这种模式。
首先是启动并发的方式:一个简单的函数,go一下就可以了:
go Handle();
接下来是消息通信:
ch := make(chan int)
go func() {
ch <- 0
}()fmt.Println(<-ch)
这里创建了一个通信通道(make(chan int)),有了这个通道,通信的双方就可以彼此发消息。
不显式声明的接口与实现之间的关系
或许是我少见多怪,Go语言是我接触到的第一个无需显式声明接口和实现二者关系的强类型语言。比如,定义这样一个接口:
type Runnable interface {
Run()
}然后,是一个使用了这个接口的函数
func RunTo(r Runnable, place string) {
r.Run()
fmt.Println("Get to " + place)
}之后,再来定义一个类型:
type Person struct {
name string
}这个类型也有一个Run函数:
func (p *Person) Run() {
fmt.Println(p.name + " is running")
}从代码层面上,Runnable和Person并没有直接的关系,唯一的关系就是二者都有Run函数。即便如此,我们依然可以把Person当做Runnable来使用:
p := new(Person)
p.name = "dreamhead"
RunTo(p, "Xi'an")只要实现者拥有接口定义的方法,就可以当做这个接口来用。如果熟悉Ruby这样的动态类型语言,你会发现这种说法几乎同Duck Typing如出一辙,只不过,在Ruby里没有“接口”而已。
从实现的角度,这个问题并不难理解,所有这一切都是编译器替我们完成的。如同类型推演一样,编译器让我们不必将重复概念反复来说,同时还保证了,不会有“上错花轿”的情况出现。
小结
Anders Hejlsberg总结了程序设计语言发展的三大趋势:声明式、动态性和并发。
声明式,包括函数式编程和DSL。函数式编程自不必说,DSL实际上事关表达性,Go简化代码编写的方式就是在这个方向迈出的步伐。动态性,并不仅仅是动态语言,动态语言与静态语言之间的互相汲取营养,类型推演与不显式声明接口和实现关系,便是向着这个方向迈进。至于并发,无需多提。
总的说来,Go语言的语言设计本身是符合现代程序设计语言发展趋势的。







