好的名字总是能为代码的可读性做出重大贡献,而这种贡献是通过对事物进行抽象实现的。
想想一下我们平常说话时所用的语言,比如说“我家的狗跑的很快”,“家”、“狗”和“跑”都是抽象,它们分别代表了不同的含义,如果不适用这几个抽
象的词汇,而是直接说它后面所代表的含义,恐怕几十句话都说不完。不信的话,你可以试试定义一下什么是狗,什么是跑,保证不是那么容易的事。
抽象可以说是软件设计和开发中的核心概念,对变量、方法、类、接口、包等元素的命名,都包含了抽象过程。例如方法,方法的名字应等价于方法内部所有
代码的功能,也就是说方法的名字是方法内代码功能的抽象。于是,不管是在交流还是记忆上,我们都可以使用方法名来指代它内部的功能。
由于人的记忆力和理解能力都是有限的,据研究称,人最多之能同时处理7个左右的事物。因此对于复杂的软件系统,抽象就成为让人能够理解的重要手段。
在代码量较小的系统中,可能抽象也比较少,理解起来比较容易,变量、方法和类基本就能处理了;而在大一些的系统中,由于抽象较多,因此需要更多的抽象层
次,使得在每一个抽象层次上都能有比较少的抽象概念,以利于理解,于是出现了诸如分层、子系统、组件、库等等。同时在类和方法内部,也会有分层次的抽象,
继承就是类的一个抽象层次关系,而方法之中调用其它方法,也是抽象层次的一种表现。
例如,狗这个类可以有很多不同子类,每一种代表不同品种的狗。从上往下看,子类是父类的泛化,是在父类上添加更多的细节;而从下往上看,父类则是子
类的抽象,集合了更多子类的相同之处。在类的层次结构中,每一级的父类都会包含更多的抽象和更少的细节。因此往往父类比子类更能表达这个层次结构的核心概
念,也就应该更谨慎的选择一个好的名字,来表达这种核心概念。同时子类的名字应该能够体现它的子类的共性,同时还应能够区分与其它同级子类的不同。
而在方法中,我们有一种使方法更具可读性的方式,就是让方法中的每一行代码都处于相同的抽象级别。例如,“我家的那只会叫的哺乳类四腿……动物跑的
很快”这句话就很难理解,因为你的思维需要在不同的抽象级别上跳来跳去,“家”和“跑”都是可以马上理解的概念,而“会叫的哺乳类四腿……动物”则是需要
思考一下才能理解的。因此比较简单的做法就是将这个细节抽象称一个名字“狗”,将之提取出来,形成另一个概念(也就是方法)。于是,系统中的方法本身也就
有了不同的抽象层次,有些关注与调用语言和平台的API来实现某些操作,有些则会调用这些方法实现更高层次的逻辑。在面向过程的设计中,最后的结果就是
main函数,而在OO设计中,可能是某个类的public方法。
命名好坏对可读性的影响,实际上是抽象方式对人的思维方式造成的影响。如果你找不到一个好的名字,通常就意味着你对问题的抽象是不恰当的,隐含的意思就是你对问题的理解不够深入或者理解错误。
--------------------------------------------------------------------------------------------------------------------------------------
添加示例代码:
原来的代码
Code
function start()
{
var url = window.content.location.href;
var html = window.content.document.documentElement.innerHTML;
try
{
var entry = new InfoQEntry(html,url);
var title = entry.getTitle();
var finalHtml = entry.getFinalHtml();
Services.copyToClipboard(finalHtml);
if(!userAccount || !userAccount.remember)
{
var account = Services.getUsernameAndPassword();
if(account.ok)
{
userAccount = account;
}
else
{
return;
}
}
var progress = new ProgressBar(window.content.document);
var gDoc = new GoogleDoc(userAccount.username, userAccount.password);
gDoc.beforeUploadHtml = function() {progress.show('Uploading');};
gDoc.afterUploadHtml = function() {progress.hide();};
var docUrl = gDoc.uploadHtml(title, finalHtml);
if(docUrl)
{
window.open(docUrl);
}
}
catch(e)
{
if(e == 'login failed')
userAccount = null;
alert(e);
}
}
现在的代码:
Code
function start()
{
var htmlWindow = new HtmlWindow(window);
var addon = new Addon(htmlWindow);
try
{
addon.extractAndUploadToGoogleDoc();
}
catch(exception)
{
handleException(exception);
}
}
function handleException(exception)
{
alert(exception);
}
function Addon(htmlWindow)
{
this.htmlWindow = htmlWindow;
this.htmlDocument = htmlWindow.htmlDocument();
}
Addon.prototype.extractAndUploadToGoogleDoc = function()
{
var entry = InfoQEntryFactory.create(this.htmlDocument);
var account = this.getUserAccount();
if(account)
{
var ticket = this.loginGoogle(account);
var gdocUrl = this.uploadToGoogleDoc(ticket, entry);
this.open(gdocUrl);
}
}
Addon.prototype.getUserAccount = function()
{
return Services.getUsernameAndPassword();
}
Addon.prototype.uploadToGoogleDoc = function(ticket, entry)
{
var progressBar = new ProgressBar(this.htmlDocument, "Uploading");
var gDoc = new GoogleDoc(ticket, entry.getTitle(), entry.getContent());
gDoc.uploading = function() {progressBar.show();};
gDoc.uploaded = function() {progressBar.hide();};
return gDoc.upload();
}
Addon.prototype.loginGoogle = function(account)
{
var googleAccount = new GoogleAccount(account.username, account.password);
return googleAccount.authenticate();
}
Addon.prototype.open = function(url)
{
this.htmlWindow.newTab(url);
} 可以看到,原来的代码中,start方法有很多行,有些行做得是一件事,可以抽象出方法,提取出的方法又可以组合成一些类,这样每个类、每个方法都比较短,且方法中的代码处于同一抽象级别。
比如这个方法
Addon.prototype.open = function(url)
{
this.htmlWindow.newTab(url);
}
只含有一行代码,但是形成方法之后,open就代表了抽象出来的动作open url,而这一行代码只是实现这个动作的细节。这样,不仅可以很容易的替换实现细节,而且还能够重用这个抽象动作。
再看这个方法
function handleException(exception)
{
alert(exception);
}
handleException这个名字反映了我们的意图是要处理这个异常,而目前处理的方式是alert一下。但是如果不要这个方法,直接把alert内联到调用函数中,就很难表明alert是一种处理异常的方式,而不是调试或者做别的事情。