调整Virtual Box硬盘大小

2011/11/15

我在Mac下使用Virtual Box安装Win7的虚拟机。因为之前装过Win7的32位版。现在因为机器内存升到8G,就可以划出4G来支持Win7虚拟机。所以就重新安装了Win7的64位版。在创建虚拟机的硬盘时,我选择了Virtual Box的默认容量20G。我看到Virtual Box告知的是这个硬盘容量是可以动态调整的,谁知道这其实是一个误导。这里所谓的动态调整并不能超过设置的值,即我设置的20G是存储分配的上限。所谓动态分配其实是一种节约磁盘空间的做法。意思是当我们在创建虚拟机的硬盘时,可以设置足够大。只要这个硬盘没有用完,这些空间是可以被主机所共享的。而Win7的64位版所占用的硬盘空间又远远超过了32位版。在安装了操作系统后,剩下的空间就不多了。在安装了SQL Server 2008后,再想完整安装Visual Studio 2010,空间就不够了。我之前认为硬盘容量可以动态调整,所以在Visual Studio提示空间容量不够时,我仍然选择“继续”,结果安装错误。

那么,该怎么解决这一问题呢?重新安装吗?那就实在太悲催了。虽然现在并没有安装什么软件,但安装操作系统和数据库就挺耗时的。于是,我试着调整Virtual Box的硬盘大小。可是在Virtual Box的管理工具中,并没有找到修改硬盘大小的选项。通过Google,我找到了通过命令行修改磁盘空间的办法,前提是Virtual Box的版本必须是4.0及以上。

这个命令:

VBoxManage modifyhd uuid –resize 40960

这里的40960就是你要调整的容量,即40G。命令中的uuid也可以用vid文件名代替。不过我的文件名不幸包含了空格。或许通过双引号或"/“可以支持空格的文件名,但我没有尝试,因为使用uuid是更好的做法。至于命令行的输入,在Mac下,直接在Terminal下输入即可。因为安装了Virtual Box时,会将VBoxManage添加到路径中,Terminal是可以识别该命令的。

要使用uuid,则需要获得当前虚拟机的uuid。在管理工具中无法获得,因此,我们应通过命令行获得:

VBoxManage list hdds

该命令会显示所有的虚拟硬盘。你可以通过Location来判断你要调整的硬盘。获得uuid,就可以通过modifyhd调整硬盘大小了。注意,在调整硬盘之前,一定要先关闭该虚拟机。

补记:当我通过modifyhd命令调整了磁盘空间后,通过管理工具查看vid文件,容量确实发生了变化,达到了预期的结果。于是我认为可以高枕无忧了。哪知道在Virtual Box下打开该虚拟机后,看到C盘的空间仍然是20G。这让我不禁郁闷不已。于是我想,可能在Virtual Box中还有什么设置。可是看了所有Virtual Box的管理菜单,都没有能够找到。我甚至在Storage中,移除对vid文件的引用,并重新加载,仍然没有变化。我又尝试着重新启动Virtual Box,甚至重新启动计算机,还是如此。究竟问题出在哪儿呢?正在百思不得其解,甚至打算删除该虚拟机,重新安装。可是想到还要去安装操作系统,以及相关软件,就不寒而栗。于是打算退而求其次,在Virtual Box中为其增加一块磁盘。结果,增加之后,启动虚拟机,发现并没有在计算机中增加新的磁盘。突然灵机一动,想到是否是计算机管理中的磁盘分区管理的问题呢。于是,打开Win7"计算机管理”中的“磁盘管理”,发现新增加的磁盘显示为未分配,而原来的磁盘容量赫然为40G,其中却有20G未曾分配。于是选择扩展磁盘,失踪的20G磁盘果然被找了回来。Virtual Box的硬盘大小调整终于取得成功。

因此,通过Virtual Box命令对磁盘进行调整后,一定要记得在Win7操作系统中扩展磁盘。这就好似你为你的计算机新添加了一块磁盘一样,需要进行同样的操作。

Tags: Posted in 工具箱1 条评论

LA工作第二周体会

2011/11/07

第一周的新鲜感过去后,第二周我就能够以平常心来面对周围的新环境了。除了语言和饮食不同,终究还是软件开发的工作,一定是要遵循软件开发的规律的。在这短短的两周时间内,我谈不上有什么收获,能够让我的能力再上一个台阶。但一些体会还是有的,虽然有些散乱,仿佛思想的片段,如野马,如浮云,若能及时捕捉并加以记录,这些体会就能够潜移默化地改变自己,或许也能改变阅读这篇博客的你。

这一周,我打算谈一谈我对能力的体会。作为一名Dev,能力是自己的立身之本,也是养家糊口的必须技能。我常常觉得,作为一名还算不错的技术人员,要挣大钱还是不容易的,但心里至少不会有失业的恐慌,总觉得自己身有一技之长,到哪里不能混口饭吃呢。所以只要不是被刺激,心态总还是比较淡定的。然而,这其中暗藏的风险就是,我的生活、我的家庭乃至我的未来就完全绑在这一根绳子上了。如果能力跟不上时代的发展,我们就会成为死在沙滩上的前浪,连一个印子都留不下来。这么一想,能力的打磨就至关重要了。那么,要成为一名优秀的Dev,需要具备哪些技能呢?在这一周通过和美国的同事Pair,我的一些观点得到了实践的印证。

1、专和博的能力

毫无疑问,专是必须的。没有专,只有博,就好比建在沙滩上的城堡,经不得风吹雨打,很容易坍塌。若要具备专的能力,就需根据自己的性格、兴趣以及工作特征来选择需要攻坚的方向,例如算法、网络、数据库、业务分析、架构设计等。这个无需多言。那么博呢?或许对于某些软件开发工作而言,“博”并非必备,但就一般的项目开发来说,确实需要具备更为广博的知识。这年头,只知道编码的程序员很难获得更好的机会。编码能力是基本,但如果不了解许多与编码有关的知识,例如设计、测试、数据库、构建脚本、工具,就很难参与到项目开发中。这些能力并不需要程序员一定要掌握、精通,但必须有所了解,并根据项目情况,决定是否需要深入学习。例如,在前一个项目中,我第一次接触到Cucumber,学会如何编写Regression Test的Feature。到了这个项目中,我要编写Twist的Test就要容易许多了。至少我不会茫然。当然,对于Regression Test而言,我还有很多困惑与体会要说,希望能有时间就这个问题专门写一篇博客。又例如对于编程语言。也许作为主流语言而言,了解Java或C#其中一门就已经很不错了。如果你一直从事.NET开发,似乎也没有学习Java的必要。可是为什么不去尝试一下呢。即使无需使用Java开发项目,了解Java的实现机制,对于开拓自己的眼界总是有好处的。何况对于项目开发而言,不定哪一天就需要你开发Java项目了。同理,我们不能只局限于一类语言。例如了解动态语言,函数语言等,了解它们的新奇之处,一定会对自己的编码能力有很大的帮助。现代的软件项目开发,越来越向着多语言开发的方向前进了,掌握多个语言,多个开发平台已经成为了程序员的必备要求。

2、学习能力

正是因为需要博,学习能力才显得如此的重要。坦白说来,现在有多少开发人员运用的知识都是自己的教师传授的呢?具备超强自学能力的程序员,即使起点很低,将来一定会走在其他程序员的前面。这是一场马拉松赛跑,比的是耐力、韧劲,当然还要有好的方法。在LA的第二周,我和Thoughtworks美国的一位老员工Sheroy一起Pair。我们需要对项目进行性能测试。我们选择了JMeter作为性能测试工具。这个工具我们事先都没有用过。不过,另外一个团队的成员曾经使用JMeter写过一个Test Plan。我们就参照着这个Test Plan以及JMeter提供的文档,开始学着使用JMeter来编写性能测试的计划。经过一天的实践,我们很好地了解JMeter的基础知识,并能够比较好的完成任务。这样的例子在我们公司俯首即是。人人皆善于学习,乐于学习。我们有很好的学习氛围,我们每天有Lunch Session,我们愿意在项目开发中尝试一些新工具或者新框架,我们愿意为新的技术去做Spike。正是因为这种学习的劲头,我们可以快速地进入项目,快速地掌握新的知识。坦言之,我在进入公司之后,曾经以为自己还算不错的学习能力,在公司同事面前就显得相形见拙了。我觉得自己的学习能力不够用了。这个压力是个好事,因为它可以促进我不断前进。

3、解决问题的能力

事实上,这个能力与学习能力一脉相承。然而,它们又不完全相同。若要具备相当强的解决问题的能力,必然具备好的学习能力。因为很多问题是我们不曾遇见过的,也可能是我们在使用新工具、新语言、新框架中面临的问题。如果没有好的学习能力,就很难找到解决问题的钥匙。然而,仅有学习能力是不够的,解决问题需要方法。例如通过调试,通过查看日志,或者有效地搜索Google。解决问题还需要经验,具有丰富经验的开发人员即使面临新问题,也能够根据过去的经验找到快速解决问题的途径。仍然是这一周的开发工作。我和Sheroy在用JMeter写性能测试时,发现同时启动多个线程模拟并发用户登录时,有的登录行为出现了错误。我们对于登录的设置是正确的。如果单独执行JMeter的测试计划,也没有任何问题;但在Jenkins上执行时,就会出现这样的错误。我没有想到任何解决办法。但Sheroy通过分析执行日志,敏锐地发现登录行为的错误总是发生在上一个持续集成任务执行完毕之后。这个任务是用于完成性能测试环境的部署。这个部署任务与性能测试任务是串行执行的,性能测试在部署之后执行。Sheroy认为,可能是部署刚刚完成,性能测试任务就立刻执行,导致登录的请求未能得到服务端的正确响应。他尝试在JMeter测试计划中增加了一个等待时间,问题就迎刃而解了。

整体来看,解决问题需要耐心、细致,善于从普遍性中找到特殊性,可能这个特殊性隐藏得非常深,那么就需要去比较正常和异常情况下,它们的环境、前置条件究竟有何不同?在什么情况下,这个问题会反复出现?只要找到了这种特殊性,往往就能发现端倪,进而想到解决问题的方案。就这一点而言,我还存在许多不足。

4、部署环境的能力

毫无疑问,这是我的短板。我最讨厌的就是配置、安装与部署。我甚至没有耐心去看安装指南、配置向导。尤其是对于一些开源框架或工具,在文档极度缺乏的情况下,我总是难以快速地完成框架或工具的搭建。相对而言,我更喜欢一键式安装的傻瓜做法。可是在现在的软件项目开发中,我们常常不限于只使用一个工具,而且使用的工具也不只限于图形化界面。也许有人会说,开发人员只需要关注自己的编码能力就可以了。可是我们开发出来的软件总是需要部署才能正常工作的,如果对软件的部署不了解,怎么交付给客户使用呢。也许有人又会说,这可以交给专门的部署人员来做啊!那么,作为实现功能的你而言,你怎么给你的部署人员说明整个部署流程?而且,若要顺利地进行软件开发,没有一个完整的开发环境,开发人员又该怎样开展工作呢?因此,开发人员应该具备一定的部署环境的能力。尤其随着DevOps变得越来越重要,这项能力也会扮演着越来越关键的角色。

5、交流能力

不可避免,还是要提到交流能力。可以说,对于这一能力,无论如何强调都不过分。我在LA工作的这两周,因为语言问题已经强烈地感受到了这一点。就在本周四,我需要完成Story 271,但我对Story的业务逻辑了解得不够,所以就找到我们的BA Jacky。他是一位台湾人,不过几乎已经不怎么会说国语了。由于我对这一部分业务缺乏足够的了解,所以我们之间的交流从我事先想象的10分钟,延长到了半个小时,并在Jacky连说带画的努力阐释下,我才弄明白了这个Story要求我们做的到底是什么。这就是交流的必要性。我曾经接触过一些刚刚踏入这个行业的程序员,他们都愿意努力的学习,但在交流能力上普遍欠缺。一个问题难以表达清楚,也很难获得正确的理解。

整体而言,无论是哪方面的能力,提升都是无止境的。能力是我们的立身之本,提升能力就能提升我们的价值,同时,也能够改善我们的生活,进而改善我们这个行业。这就是我的一点体会。

Tags:, Posted in 程序人生1 条评论

LA工作第一周体会

2011/11/01

与一群国外的Dev一起工作是我从未有过的经历,在陌生的国度,陌生的团队,陌生的客户,做着陌生的项目,对我而言,Everything is new。我们在客户这里,仍然采用典型的敏捷方式:故事墙、站会、用户故事、结对编程、持续集成、TDD甚至BDD……几乎所有的敏捷实践我们都会运用。在加入这个项目时,已经进行到第7个迭代,整个项目的框架已经比较成熟。我们的任务是尽快熟悉业务和整个技术框架,并为新的项目做好充分准备。在接近3周的时间内,我们都会和客户以及美国Thoughtworks的同事们进行Pair,通过实际的开发来熟悉和了解业务与技术。然后,在剩下的两周内为新项目做Inception。

项目是基于.NET Framework 4.0进行开发的,采用的技术包括C#, VB.NET, Ext Js, SQL Server 2008。IoC容器为Structure Map,NUnit作为单元测试框架,Moq作为Mock测试框架。我们使用了Jenkins(即Hudson)作为持续集成工具,使用了Thoughtworks的产品Twist作为回归测试和集成测试工具,并使用了Powershell作为构建脚本,Git作为源代码控制工具。

来到LA的第一周。除了第一天参加了几次Meeting,了解了整个项目的情况尤其是业务逻辑之外,第二天就迅速进入团队,开始结对编程。整个第一周,我完成了2个Bug Fix,同时,对Regression Test出现的问题进行了修复,并参与了一个Story的开发。在这一周,我并没有Switch Pair,一直是和客户这边的一位Dev(名叫Andrew)进行合作。

回顾这一周的工作,我的感受如下:

1)业务逻辑的了解比技术更重要

作为一名Dev,可能首先会想到项目会使用什么技术,我对这些技术了解吗?总之,会首先关注一切与技术有关的东西。在进入项目之前,我特别关注了这些内容,并抓紧一切时间为这些技术做储备。当然,我们也希望了解业务逻辑,但由于前期准备时缺乏这方面的条件,我们所能了解的就是项目与Healthcare有关,项目内容有些接近CMS。然而,到了项目之后,我们才发现,技术并非决定你能否快速进入团队,并开始开发和实现的关键。如果不了解业务逻辑,不明确领域术语,我们将很难进行沟通和交流。尤其对于现在这个项目,由于项目已经做了一部分。对领域的了解就更加重要了。对于一名有着多年经验的Dev来讲,其实技术并不会成为制约你进行项目开发的主要瓶颈。在这个项目中,有很多技术都不是我掌握的,但我们仍然可以快速进入开发活动。这是因为Pair Programming可以很好地完成知识共享和传递,我的Pair可以像Mentor一样来带领我迅速进入状态。

2)交流是项目开发的根本

在国内做项目,交流或许也会成为障碍,但因为语言相同的缘故,我们往往将交流活动忽略了,似乎觉得这是顺乎自然的事情。在这一周的工作中,我仿佛进入了另一个世界,耳边充斥地都是自己难以理解的语言。虽然我有一定的英语基础,但真正和这些母语为英语的Dev合作时,才发现我所掌握的英语单词和语法都变得不够用了。我的舌头似乎也打结了,我很难理解Pair所要表达的意思,自己也很难向Pair表达我的意见。这就导致开发效率受到很大影响。即使任务已经完成,整个实现对我而言,仍然似是而非,还需要自己下来看Story的描述,看源代码。例如,在我们项目中已经实现了比较好的Validation机制,但为了实现一个相对较小的Story,由于交流的问题,我们的实现被严重阻碍了。

此外,在我们项目中完全具备现场客户的条件,因而交流更是成为重中之重。我们的BA团队既有TW的,也有客户的。他们都写了很不错的User Story。在我们实现这些User Story时,如有不明白之处,都需要尽快咨询BA,通过交流消除歧义。而在实现之后,必须和BA做Show Case,以尽快获得反馈。这一点非常重要,也是敏捷的核心价值观体现。

3)好的习惯很重要

第一周,和我Pair的Andrew是来自客户的一位实习生。他刚从大学毕业,进入项目大约三个月时间。在大学期间,他只学习了C++,对.NET、Javascript以及CI等内容都不熟悉。换言之,他现在所掌握的所有技术,都是在项目中学到的。虽然是这样一个Intern,但我发现他已经具备了非常良好的编码素质。在开始一个Story时,他会首先在Twist编写Regrssion Test Sceinario。而在实现代码时,也会尝试着通过Unit Test来驱动实现。在提交代码时,会合理地运用Git命令。例如在开发前,会通过Git Status检查当前状态,看是否有变更。在Commit时,如果发现提交的内容有冲突,他会非常慎重地处理Merge。编写代码时,会严格遵循我们制定的编码规范。虽然,他在开发方面的经验还有很多欠缺,但无疑已经有了一个好的开始。我想,通过这个项目的锻炼,在Thoughtworkers的言传身教下,只要他愿意继续努力,应该会有一个很好的发展前景。

同时,对于团队而言,这样良好的编码习惯,就像所谓的“童子军军规”一般影响这团队的每个人,我们欠下的Technical Debt就会少很多,这对于后期的维护、修改以及将来后续的开发,都有非常大的帮助。

4)学会寻求帮助

每个人所掌握的知识总是有限的。或许你的能力足够让你解决任何问题,但考虑成本问题,若能适当地寻求帮助,你既能够快速完成任务,同样能够学到你希望学到的知识。即使寻求帮助后,没有得到你所期望的结果,至少说明,我们可以拒绝某些选项,这同样可以节约时间成本。最关键的一点需要时刻记住,我们是一个Team。

在第一周的Story开发中,由于我和Andrew都对Validation的机制不太熟悉。我们尝试了多种方法,希望解决问题,但都不奏效,这样反复的尝试已经耽搁了大约一天的时间。后来,我们主动找到另一位Thoughtworker-Eric,他已经在项目呆了较长时间。了解我们的问题后,Eric仅用了不到10分钟的时间就解决了这个问题。事实上,这个问题的解决方案需要一个小技巧,在Label中设定我们事先指定的CSSClass,就可以显示那些Validation Message。

5)知识分享

毫无疑问,只有充分的知识分享,才能有效发挥团队的作用。尤其对于新加入团队的成员,如果没有足够好的知识分享,或者团队的老成员不具备这方面的意识,将自己拥有的知识传递给新成员,新成员就很难融入团队,也很难快速地贡献自己的Effort。在知识分享的过程中,通过Meeting和Session,或者阅读文档的方式当然是有效的。但事实上,即使你参加一天的Session,或阅读一天的文档,都不如和你的Pair实际做一个Story,对知识的分享来得更好。根据我自己的经验,所谓Session或文档的方式,更适合介绍一些领域背景知识,或宏观的系统架构。细节知识必须在实践过程中获得。这时,Pair Programming就显得非常重要了。

在选择自己的Pair时,也需要针对实际情况做出调整。例如与新手Pair的必须是相对有经验的Dev(当然也可以和QA或BA结对)。另外,也需要适当地Switch Pair。在这个项目中,我就体会到这一点的重要性。第一周的3天时间,和我Pair的都是Andrew。虽然他已经进入项目三个多月,但毕竟还缺乏一定的经验。而我作为项目新人,更是很多内容都不了解。这样的Pair组合,效果可想而知。在这个过程中,本来我们应该进行Switch,但因为Story的关系,其他Pair也有自己的任务,这个Switch活动就被推迟到了第二周。事实上,在第二周的第一天,我和Eric合作,效果就非常好。整个Velocity得到了较大的提高。

Tags:, Posted in 书评撷英我抢沙发

并行与死锁

2011/10/10

我们希望采用并行的方式在本地运行单元测试,从而减少测试时间,提高开发人员的工作效率。我们使用了线程池来提供多线程的并行任务。通过配置启动多个线程,并以程序集为单位,启动TestRunner:

var executorWrapper = new ExcetorWrapper(assemblyName, null, false);
var testRunner = new TestRunner(executorWrapper, new RunnerLoggerWrapper());
testRunner.RunAssembly();

其中的RunnerLoggerWrapper是一个自定义的类,实现了Xunit的IRunnerLogger。XUnit的使用并非本文描述的内容,在此略过。

因为是以程序集为单位,所以我们在启动多线程之前,会事先将需要运行的程序集放到一个队列中,然后在启动多线程之后,执行出队列操作。多线程的运行代码如下所示:

private static ManualResetEvent[] resetEvents;
private static Queue<String> assemblyQue;
private static readonly Object LockAssembly2Queue = new Object();

public void Run()
{
    for (var index = 0; index < numThreads; index++)
    {
        resetEvents[index] = new ManualResetEvent(false);
        ThreadPool.QueueUserWorkItem(DoWork, index);
    }
    WaitForAllManualEvent();
}
private void WaitForAllManualEvent()
{
    if (Thread.CurrentThread.ApartmentState = ApartmentState.STA)
    {
        foreach (var manualResetEvent in resetEvents)
        {
            WaitHandle.WaitAny(new WaitHandle[]{manualResetEvent});
        }
    }
    else
    {
        WaitHandle.WaitAll(resetEvents);
    }
}
private static void DoWork(Object index)
{
    Thread.CurrentThread.ApartmentState = ApartmentState.STA;
    while (true)
    {
        string currentAssemblyName = null;
        lock (LockAssembly2Queue)
        {
            if (assemblyQue.Count != 0)
            {
                currentAssemblyName = assemblyQue.Dequeue();
            }
            else
            {
                resetEvents[(int)index].Set();
                Console.WriteLine("Exited current thread:{0}", Thread.CurrentThread.Name);
                break;
            }
        }
        if (currentAssemblyName != null)
        {
            new TestRunnerWrapperWithAssembly(currentAssemblyName).Runner();
        }
    }
}

由于要测试的程序集比较多,采用这种并行方式可以极大地提高运行效率。由于单元测试彼此是独立的,在并行运行时,互相没有干扰。这是我们实现判断的结果。一切看起来很美好,但在真正运行时,却出现了大量的死锁。异常信息为:

Transaction (Process ID) was deadlocked on resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

在我们的单元测试中,大多数测试需要访问的资源都是在内存中进行,但有一部分单元测试必须与数据库通信,对数据表进行读写。除了极少数特殊的测试用例外,对数据表的操作都放在事务中进行,并在执行完毕后,通过回滚事务,避免对真实数据的提交,保证单元测试不会影响数据库。

注:单元测试应该访问数据库吗?这其实还有待确认。在《修改代码的艺术》一书中,Feathers这样写道:

单元测试运行得快。运行得不快的不是单元测试。

有些测试容易跟单元测试混淆起来。譬如下面这些测试就不是单元测试:

(1)跟数据库有交互;

(2)进行了网络间通信;

(3)调用了文件系统;

(4)需要你对环境作特定的准备(如编辑配置文件)才能运行的。

以上可以看到Feathers的态度是单元测试不应与外部资源进行交互。显然,如果出现了这些交互,就应该采用Mock的方式来模拟对外部资源的访问。然而,某些实现功能却是与外部资源息息相关,又或者我们测试的目的本身就是验证对外部资源的访问是否正确。从测试的范围来看,它们仍然算是单元测试,但因其特殊性,而应该将这些测试放到系统测试的范畴。在持续集成中,我们常常用金字塔来表示单元测试、系统测试和集成测试的数量。如下图所示:

test

单元测试的数量最多,如果还需要访问外部资源,就会严重影响运行单元测试的速度。关于单元测试、Mock等内容,我希望在以后的文章里详细论述。

在我们的项目中,是通过注入Fixture的形式生成测试数据。例如,我们可能希望注入Client、Associate等对象,从而完成对某些行为的测试。例如:

[Fixture(typeof(client_hastings))]
public Client client;

[Fixture(typeof(Samuel))]
public Associate Samuel;

通过Fixture准备数据时,如果采用了持久化方式,则意味着需要对数据表进行操作。如上代码就可能操作多张表,例如对Client表和Associate表进行写操作。由于单元测试采用并行方式进行。假设存在两个单元测试均需要对Client和Associate注入Fixture,生成测试数据;并且不幸的是,这两个测试用例准备数据的顺序刚好相反,即A测试用例的顺序为Client->Associate,B测试用例的顺序为Associate->Client,就可能发生死锁。

为什么?让我们分析数据库发生死锁的情况。它必然是多个进程(或线程)对两个或两个以上的资源形成了交叉访问。例如进程A在占有了资源1的同时,还需要访问资源2;与此同时,进程B在占有了资源2的同时,需要访问资源1。由于资源1已经被进程A占用,无法释放,进程B就会等待;而进程A希望访问的资源2又被等待中的进程B持有;二者互不相让,最终产生死锁。这正是并行运行单元测试导致死锁的根本原因。我们可以运行SQL Server Profiler来监视数据库的执行。注意,倘若需要跟踪死锁的情况,需要在Trace Properties中勾选“Deadlock Graph”和“Lock: Deallock”选项,如下图所示:

profile_trace

创建Trace后,利用并行方式运行单元测试,可能得到这样的Deadlock graph:

deadlock

图中,椭圆代表进程(线程),矩形代表资源。左边的椭圆打了一把叉,说明是竞争失败的进程(线程)。从椭圆出发,箭头所指的资源,代表进程请求的资源;而发出箭头的资源,则代表箭头指向的进程持有该资源。可以发现,两个进程与两个资源之间的箭头,事实上形成了一个封闭的环。这正是死锁的典型表现。

当我们将单元测试的Fixture注入顺序保持一致时,这样的死锁就能够避免了。这是一种限制,它很难被编写单元测试的开发人员所接受,即使勉强接受,仍然很容易疏漏。因此,我们的结论仍然是“不到迫不得已,单元测试不要访问外部资源”。或者说,我们可以将访问外部资源的单元测试,看成是特殊的单元测试,如果确实需要并行运行测试,以提高测试效率,可以通过引入多个Agent,以物理方式隔离资源,避免出现资源的争用导致死锁。

那么,这是否意味着我们的产品代码不够严谨,没有充分考虑并发的情况呢?不完全对。因为这里产生死锁的时机发生在准备测试数据的阶段,实际操作时,一般不会出现这种频繁对多张表进行操作的情况。然而,即使几率很低,始终存在死锁的隐患,这就为我们的开发敲响了警钟。因此在开发过程中,有必要通过对业务的分析,制订一些指导原则,通过规范写数据表操作的顺序,避免出现死锁。这是这次并行运行单元测试给我们带来的启示。

Tags:, Posted in 编码修炼1 条评论

设计匠艺模型

2011/09/25

sevenprinciple

之前,我曾提出软件设计的七个原则,即重用、扩展、变化(后改为协作)、简约、一致、分离、间接,并在去年的亚太软件研发团队管理峰会阐释过自己的想法。经过这两年的积累,这些内容逐渐丰富起来,而我也根据自己所思所想做了一些调整。我的朋友姜大胡子和刘冰对我提出的这几个原则,表示兴趣和部分认同,但同时也提出一些自己的看法。按照大胡子的意见,一个人的记忆很难记住太多的原则,最好不要超过3个。七个原则好似古龙的七种武器,听起来很拉风,其实会让人难以记忆。

通过反思这几个原则,我固然同意大胡子的看法,但让我愿意改进的根本原因,还在于我发现这几个原则其实并不在一个抽象层次上。例如,重用和扩展,其实是我们希望达成的目标,它与间接、分离等原则有着根本的不同。所以,我一直思考着该如何改进这七个原则。之前,我已经初步对这七个原则进行了划分。但我始终比较纠结,例如在“重用”原则中,我总结了一些好的设计理念。譬如说,通过粒度的划分与依赖的解耦,以保证有效的重用。从这些设计理念来看,至少在这个层次上,这几个原则又是平行的。

上次在北京邦元素咖啡的一次技术人士聚会,大胡子给出了他的一套体系。我很认同他在价值观与原则的一些看法。最近,根据自己的一些思考和深入分析,才发现之前提到的“重用”和“扩展”原则中的设计理念,其实仍然可以通过遵循一致、分离与间接原则来保障。渐渐地,这个模型开始成型了。如上图所示,我将这个简单的模型称为设计匠艺模型。

整体来看,模型分为左、右二图。左图的核心圈,描述的是“简单”价值观。这意味着我们在设计时,需要时刻体现设计的简单性。这同时也是UNIX编程艺术中反复提到的原则。将其放在价值观中,是希望突出它的核心地位。从这点来看,我这个模型与大胡子的模型完全相同。

“简单”价值观的外面,是三个主要原则(核心原则):间接、分离与一致。它们不仅是设计的重要原则,同时也是架构的重要原则,尤其是分离与一致(可以阅读我的这篇博客《软件架构的一致性》),可以通过关注点分离与架构风格一致性,成为软件架构过程中必须遵循的关键原则。这些原则没有包含“抽象”原则,是因为我认为“抽象”其实是“间接”原则的一种体现。“间接”原则的含义更广泛,事实上,它还能够有效地体现对象的封装。正如我的结论,GOF 23种设计模式中,所有模式都体现了“间接”原则。若有不同意见,欢迎PK。

右图是我们在软件设计中必须要达到的目标(或者说是尽可能达到的目标),即软件的可重用性和可扩展性。它事实上可以看做是软件系统的质量属性。在质量属性中还有其他属性,例如安全、性能、可伸缩性、可维护性等,但我认为这些属性更偏向于纯粹的架构过程。而重用与扩展则是架构与设计都需要保证的。

中间的箭头代表设计手段。虽然有很多设计的手段,但我始终认为面向对象设计的最大难点,还是在于职责的识别与分配。这正是我提出“协作”手段的原因。而对于一个好的软件系统而言,和谐的对象协作正是设计的主旋律,也是软件质量的正确保障。图中的三角形表现了“协作”原则的主要内容,包括自治对象、专家模式与场景驱动,中间的一块三角型,则是理想的对象协作模型,即形成好的对象社区。这些内容算是对整个模型的一些补充。

今天打算抛砖引玉,让大家指出模型的不妥之处。这个模型涉及到很多设计的方法、原则,透视了设计的本质,而这些内容则需要大量的篇幅来描述,而这正是我下一本书的主要内容。我会在今后的博客中陆续给出这个设计匠艺模型的细节内容。

Tags:, Posted in 设计之道我抢沙发