(翻译http://bdn.borland.com/article/0,1410,30011,00.html)
本文描述了几个简单的编程技巧,文中的讨论和代码是通用的,可以适用于C++,C#,Delphi或者Java程序员。
本文讲的技巧很容易描述:避免在一个可视的容器――例如Java的JPanel或者JDialog,Delphi/C++Builder的TForm或者TFrame――中写代码。相反,你应该在为一个单一,简单的目标设计的独立的类中写代码。
当然,合适的时候要打破任何规则。毕竟,建立规则就意味着你有机会打破它。
点击编码的危险
Borland是现在流行的拖拽组件,双击,然后立即开始写事件处理代码这种编程方式的倡导者,这种系统的好处在于你可以快速容易地写代码,危险处在于它会带你走上一条享乐主义之路――你会编写那种不可维护的“写一次,永远不读(Write once,read never)”的代码。
例如,假定你要创建一个可视的容器,它允许用户查看数据。假定你的容器要显示XML文件的解析结果,这个XML文件包含旅行者一号和二号太空飞船的信息。
这种数据可能相当复杂,例如,你或许想要控制用户只能看到旅行者1号的数据,或者只能看到旅行者二号的数据。同样,你可能想要允许用户看太空飞船经过的某一个特定星球的数据,例如木星,土星或者海王星。
显然你必须写大量的代码从XML文件中提出数据。可视化程序设计的新手会直接在一个按钮对应的事件里写这些代码:
void MyButtonResponseMethod_actionPerformed(ActionEvent e)
{
//这儿是解析文件的代码
}
假定这个方法中的代码允许用户按照他星球来排序。程序员很高兴地写了几行SAX基础代码,看起来似乎解决了问题。
但是正好这个时候斧头落了下来。嘭!斧头掉了下来,写这段代码的那个程序员被裁员了,本来他还有可能可以维护这段代码。
让我们跟随这个倒霉的程序员到不久的将来看看更大的损害。应用程序开发的下一步或许是要增加一段程序来,这段程序显示按照或者旅行者一号或者旅行者二号独立发现的东西来排序的数据。所以,另外一个按钮响应方法被创建,现在,用户有两个选项:
1. 按照星球排序
2. 按照太空飞船排序
到现在为止,一直还不错。但是如果用户想要同时按照太空飞船和星球来排序呢?很常见的解决方式是再创建第三个按钮响应方法,其中包含按照星球排序响应方法,和按照太空飞船排序响应方法中的代码。真悲惨,大块的重复代码开始出现。如果在按照星球排序的响应方法中发现一个Bug,那么这样的修改也必须应用于第三个按钮的响应方法中的那段重复的代码里。
一些人笑了:“哦,但是我从来不会作如此愚蠢的事情!”。但是我确信,聪明人有时候确实会做傻事。
现在,我们假定程序员足够聪明,不会犯这样的错误。现在看一个同等水平的问题。他们在可视化容器类中把几个排序的代码移动到他们自己的独立的方法中。或许,这样一个类的声明是这样的:
public class MyVisualContainer
{
void _SortByPlanet_actionPerformed(ActionEvent e)
void _SortBySpacecraft_actionPerformed(ActionEvent e)
void SortByPlanet()
void SortBySpacecraft()
void SortByPlanetAndSpaceCraft_actionPerformed(ActionEvent e)
}
这儿的问题是MyVisualContainer类已经开始挑起太重的负担!就像Jekyll医生和Hyde先生一样,它开始了双重人格的生活。它是一个单纯的可视化容器还是一个XML解析器?
这类型的代码的合适的结构应该如下:
public class MyVisualContainer
{
void _SortByPlanet_actionPerformed(ActionEvent e)
void _SortBySpacecraft_actionPerformed(ActionEvent e)
void SortByPlanetAndSpaceCraft_actionPerformed(ActionEvent e)
}
public class MyVoyagerParser
{
void SortByPlanet()
void SortBySpacecraft()
void SortByPlanetAndSpaceCraft();
}
容器中的方法应该只包含调用MyVoyagerParser类功能的代码。随着编译器和CPU的发展,增加一个额外的类所增加的消耗对于应用程序的影响可以忽略不计,在许多情况下,甚至是可度量的。
智能编程的优势
前面的部分,我概括了在按钮和菜单的事件中直接添加复杂代码的危险。现在谈谈新的编码方式的优点。
假设你想要在新的程序中重用以前写的一段代码,但是现在有不同的界面。如果写的代码合适,那么你移植到新的应用程序就会容易。例如,在最后的那个例子里,你可以简单的重用MyVoyagerParse类。只要把它插入到你的新程序里,用下面的简单代码调用即可:
MyVoyageParse->SortByPlanet();
但是重用只是事情的一半。另一个优点是这有助于你简化代码。
我们已经明白,可视化容器类很容易产生试图支持更多功能的代码。如果你要在你的容器中增加一个全新的功能。例如,假设你想要写一些代码来向DSN报告,这些代码要用于与旅行者太空飞船联系并且发送命令。
如果程序员开始把如下代码增加到原始的可视化容器中,最糟糕的情况就会出现:
public class MyVisualContainer
{
void _SortByPlanet_actionPerformed(ActionEvent e)
void _SortBySpacecraft_actionPerformed(ActionEvent e)
void SortByPlanet()
void SortBySpacecraft()
void SortByPlanetAndSpaceCraft_actionPerformed(ActionEvent e)
void DSNCommand_actionPerformed(ActionEvent e)
void DSNTransmission_actionPerformed(ActionEvent e)
void SortDSNCommunicationsByCommand()
void SortDSNCommunicationsByTransimmision()
}
注意新版的MyVisualContainer类的最好两个方法。这个可怜的操劳过渡的类被迫担起了第三个责任(或者它是第四个?)!并且随着程序的增长,更多的任务会被分配给MyVisualContainer。
明显的解决办法是创建三个类:MyVisualContainer, MyVoyagerParser,和MyDSNParser。一个新接手这个项目的程序员可以立即找到这个项目中与的DSN相关的部分。并且可以通过一系列孤立的程序调试这部分代码。
The Tricky Bits
公平起见,我应该指出这儿描述的技术有些困难。尤其是为了提高重用性,你应该从可视的程序代码中彻底地分离出工作代码。
假定Sort by Planet类需要创建一个字符串列表并显示在列表框中,如果列表框是在可视化容器内,MyVoyagerParser类会如何访问它?
最糟的解决办法是传递列表框本身的一个拷贝。相反,你应该传递要显示在列表框中的列表。对于Delphi和C++Builder程序员,这意味着你需要传递一个TStrings对象。Java程序员或许想要床底一个DefaultListModel或者可能是一个字符串数组。
MyVoyagerParser类的其他方法可能被设计为返回字符串或者整数,这些返回值被MyVisualContainer类指派给一个Edit控件或者Label控件。在某些情况下,你或许需要迎合客户的数据结构。支持ModelView结构的语言-象JAVA可以使这样的任务更容易。
实际上,我喜欢写这样的代码――它可以使我的工作类和我的可视化的容器共享数据更加容易。在绝大多数情况下,办法很容易找到。结果是更容易重用的类,它们可以象钟表那么精确地结合在一起。
结论
通过本文你已经学会了一个简单的技巧,你可以用它来改善你的代码,使它容易重用和维护。本文的观点看起来很明显,但是具有讽刺性的是,许多的聪明的程序员却很容易犯这样的错误。一个“正常的”程序员很容易写出一大砣乱七八糟的代码。即使我们向这个类增加50个方法,并且它已经具有了5到6个角色,一个内行的程序员仍然可能尽力排列它。他们可能可以让它工作起来,并且他们可能会发现所有的Bug并且修补它以便它可以正常运行。
然而,当需要在新的项目中修改原来的代码的时候,即使是聪明的程序员也可能不能解决由此产生的全部问题。例如,我曾经看到非常聪明的程序员为了移植代码――从Windows平台到Linux平台,从标准Delphi或者C ++代码向.NET平台,从Delphi或者C++向JAVA,采取了可怕的方式(hacks?)。在许多情况下,只是他们的可视化容器负担太重。有时,即使最好的程序员也可能得益于本文提到的简单技巧:
不要把代码实体放在可视化容器,而是放在为单一目的设计的类中。