怎样写出漂亮的代码?
事实上,在WP这个范围内,我还是看过一点代码的,看多了,我就觉得,怎么有些代码看着就讨厌,而另一些代码看着就很兴奋,觉得很爽。
举个例子,WP本身的代码,看着感觉还不错,至少很清楚,一般一个函数打开,基本上能看懂这个函数在说什么。主题框架hybrid,结构非常清楚,非常有条理,最后就是非常合理,一看就觉得很兴奋,热血沸腾,仿佛你能在这里面做任何事情,你能理清楚来龙去脉。
事实上,在WP这个范围内,我还是看过一点代码的,看多了,我就觉得,怎么有些代码看着就讨厌,而另一些代码看着就很兴奋,觉得很爽。
举个例子,WP本身的代码,看着感觉还不错,至少很清楚,一般一个函数打开,基本上能看懂这个函数在说什么。主题框架hybrid,结构非常清楚,非常有条理,最后就是非常合理,一看就觉得很兴奋,热血沸腾,仿佛你能在这里面做任何事情,你能理清楚来龙去脉。
本文提出问题,并不解决问题。
我在研究Extended Live Archive(ELA)插件的代码,发现一个问题,这个问题具有一定的共性,而且,我现在也没有什么好的解决办法,所以我把它描述一下,看看有没有高人高见。
当开发好一款插件的时候,要为插件撰写一个readme.txt文件,这是让用户了解你的插件的最好方式。很多插件都会在readme.txt的第一个section(description)里,附上一个简单明了的features list,以便用户可以快速扫描你插件包含的功能,决定是否选用。那么应该如何撰写features list呢?本文为您介绍。
在笔记03中,已经提到了使用Lucene进行搜索的几个必要组件:
上面已经提到了,IndexSearcher中有很多重载的search方法,不过我仔细看了一下,建议使用的并不多。1
2
3
4
5
6
7public TopFieldDocs search(Query query, Filter filter, int n, Sort sort)
public TopDocs search(Query query, Filter filter, int n)
public TopDocs search(Query query, int n)
//following low-level
public void search(Query query, HitCollector results)
public void search(Query query, Filter filter, HitCollector results)
Filter是一个抽象类,就像其名字标识的一样,该对象将会过滤搜索结果,挡住一些结果,放行另一些。如果我们在建立索引的时候,对日期按照YYYYMMDD的格式建立了索引,那么我们在搜索的时候,可以使用PrefixFilter来查询某一年YYYY的文档。另外,我们也可以使用RangeFilter来搜索某个特定字段存在于某个范围内的文档。Sort则是一个可以改变排序结果的对象,可以告诉Lucene按照某个或者某几个特定的字段排序搜索结果,还可以让Lucene将搜索结果逆序。
HitCollector是一个抽象类,其子类的对象,都要提供一个collect方法,IndexSearcher会将每个文档的id和其原始得分传递给该方法。在这里,就可以自定义对最后结果的排序算法了。这里,可以看一下默认的TopDocCollector的collect方法实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public void collect(int doc, float score) {
if (score > 0.0f) {
totalHits++;
if (reusableSD == null) {
reusableSD = new ScoreDoc(doc, score);
} else if (score >= reusableSD.score) {
// reusableSD holds the last "rejected" entry, so, if
// this new score is not better than that, there's no
// need to try inserting it
reusableSD.doc = doc;
reusableSD.score = score;
} else {
return;
}
reusableSD = (ScoreDoc) hq.insertWithOverflow(reusableSD);
}
}
默认的算法,就是根据原始分数的高低,来对搜索命中结果进行排序。实质上就是一个堆排序,这里用到的hq对象,是一个优先级队列。
每传递进来一个文档id,将该文档的分数与上一次操作结束后,得分最低的文档比较(优先级队列长度是有限的,填满后,抛弃得分最低的文档)
如果,得分较高,插入队列,并记录下这次操作被挤出队列的文档,否则,抛弃之。
如果我们自己来设定排序方法的话,我们可能先要调用IndexReader对象,取得我们计算排序所必须的字段,然后,在这个函数内实现我们的计算公式。这里,我就在想,是否可以更早一步地介入到Lucene的搜索过程中,比如这里,score已经计算好了,是否有机会介入到计算scroe之前的地方,这样,我们就可以用自己的公式来计算scroe。这只是一个想法,还没有进一步验证和研究。
今天看过的一些文章的摘抄:
Moving Lucene a step forward ——上面那篇中文的文章中提到的英文原文。
数学之美 系列九 – 如何确定网页和查询的相关性 —— TF·IDF统计法的一个基础介绍。
Lucene是允许对索引的并发操作的,具体操作时,要遵循三条简单而严格的规则:
Lucene的并发规则非常简单,而且,这样的规则基本符合我们的直觉思维,因而非常容易记忆。事实上,Lucene并不强制遵守这些规则,但是违背规则,将带来不可预测的风险,例如索引损坏。
实际操作中,一个好的做法是对于执行写操作的对象,使其单一实例化,也就是使用Singleton设计模式。Lucene的索引操作对象,都被设计为线程安全的形式,多个线程可以直接调用,而不需要额外的同步操作。这一点相当体贴。
实际上,可能没有人会故意去不遵守Lucene的并发规则,造成这样的状况,往往是意外,所以,Lucene提供了一套锁,来保护索引。
Lucene的锁以文件的形式保存在磁盘上,一共有两种锁,一种是write.lock,另一种是commit.lock。
使用Lucene建立索引,有三个主要步骤。
**提取文本。**Lucene只能对纯文本建立索引,所以,任何需要建立索引的资料,都要进行过滤处理,从中提取到纯文本。比如对于Word和PDF,我们都要使用相关API将其中的纯文本提取出来,而对于XML和HTML,则意味着要过滤掉所有的tag。
**文本分析。**要建立索引,首先要将文本分解成一个个片段,一般就是单词,当然也可能是词组,句子等。分割好的东西,可能还要进行归一化处理,以确保最大程度上的检索能力,比如,全部变成小写字母,以后搜索的时候,就能忽略大小写。这个过程对于字母文字,有个步骤,就是回归原型,像英文、德文、法文这些我稍微有点了解的语言里,一般都有“数”,“格”,“态”的变化,而同一个词的变化形式,应该被视为是一个词,而不是不同的词。对于汉语这样的没有变形的语言,这方面就非常方便了,但是汉语却有着另一个不方便的地方,就是汉语的最小单位不是字,而是词。也即汉语需要进行分词处理。英文单词使用空格分隔,分词要简单得多得多。除却这些步骤,还有一个共同的步骤就是删除stop words,简单说就是无意义词,一般来说就是数词,量词,助词,介词,代词等等虚词。
**将索引写入磁盘。**Lucene将分析好的文本使用一种叫做倒排索引的数据结构写入到磁盘中。倒排索引(inverted index)的建立,完全是为了搜索的方便。如果说,“正排索引”可以回答你一个问题,“这个文档中,包含了哪些关键词?”,那么“倒排索引”回答了你一个相反的问题,就是“哪些文档,包含了关键词X?”。倒排索引是当今所有主流搜索引擎的核心结构,而这些搜索引擎之所以不同,是因为在建立倒排索引时所附加的独特的参数,比如著名的Google PageRank。这些参数决定了最终搜索结果的排序。
在笔记02中,写了一些建立索引的基本元件,但是没有详细研究。这里从逻辑上来看一下Lucene的索引。Lucene的索引从逻辑上看是一个整体,内部的结构却很复杂,可以很方便的合并,删除,而且并发访问的能力很强。索引的内部,最小的单位是字段(Field),然后一些字段的集合是文档(Document)。这个文档和我们对其建立索引的那个“文档”是不同的,这是Lucene索引内部组织的一种形式,按照我的理解,就是一组相关的字段,规整在一起,就是一个Lucene Document。举个例子,一个Lucene Document包含标题,作者,日期,关键字,内容五个字段,具体字段的数量,应该是在建立索引的时候决定的,可以根据需要添加足够多的字段。比如还可以添加网址,分类信息等等。事实上,在建立索引完毕后,我们从索引中搜索出来的最终结果,是Lucene Document,而不是原本我们对其建立索引的文件。这些Document通过其内部字段信息,和真正的文件建立关联,比如我们在Google中,搜到的结果,都带有一个超链接指向真正存在于互联网某个角落的网页。
字段是一种数据结构,包含一个名称(name),和对应的值(value),可以看作是一种键值对。字段的值可以是普通文本,比如一个网页的全部正文部分,在构造一个Field的时候,这样的值由java.lang.String或者java.io.Reader封装,这样的字段在建立索引时,会经历上文中第二个步骤——文本分析;字段值也可以是原子化的关键字,这样的值,在建立索引的时候,不会被分析,这样的值往往是用来标识一个文件的日期,url等信息。字段是否需要存储到索引中,是可选的,存储的字段在搜索时也会包含于搜索结果中。
根据一个字段是否需要分析,是否需要索引,是否需要存储,字段应该一共分成八种。但是有些种类是没有意义的,比如需要分析,但是不需要索引的字段,实际上是不存在的,所以,实际常用的字段只有四种类型。
Keyword——这类字段不会被分析,但是会被索引并且会被保存。这类字段的完整性需要收到保护,比如网址,电话号码,日期,人名等等。
UnIndexed——这类字段不会被分析,也不会被索引,但是会被保存。这种字段一般用来存储你需要显示在搜索结果中的内容,但是你往往不需要直接搜索它们的值。我个人对这个种类是存在疑问的,我基本上想不出来有哪些信息满足这种特征的。搜索引擎发展到今天,我们可能在Google搜索框中敲入任何东西,曾经看到一篇文章,里面有句话,“……你可能无法想象,有多么高的比例的人,在使用Google的时候,在搜索框中直接打入一个完整的网址……”,确实是这样,像我爸这类用户,把Google设成首页,完全无视浏览器的地址栏,跑题了。如果说非有这么一种字段,我想,可能是标识了这个搜索结果的属性的信息。比如,你搜索“Charles”,返回结果里有一些信息,说“性别:男”,“物种:人类”,“居住地:地球”,这种信息应该满足条件了,但是不是现实生活中的例子,呵呵,搜索引擎要真那么恐怖,某些人就惨了。
UnStored——这类字段与Unindex正好完全相反。会被分析,并且索引,但是不会被保存。比如,一本书的全部内容。一个网页的全部正文。
Text——这类字段会被分析,索引,如果是纯文本,也会被存储。符合这个种类的例子,我能想到的例子,比如一篇论文的摘要。这样的文本段落不但有高密度的关键字,而且几乎可以完整的概述一个文件的内容。
上述的四个种类,在早期的Lucene版本中,本身就有对应的类,构造字段的时候直接使用上面的名字构造相应的对象即可。但是在最新版本的Lucene中,使用了一种更为统一的形式,也即只有Field一个类,然后使用一些参数来描述这个字段的属性,通过参数组合,可以组合出各种类别,甚至那四种不存在的类别理论上也是可以组合出来。
现在的Field构造函数原型是如下样子的:1
public Field(String name, String value, Store store, Index index)
我想,有了上面的分析,这个构造函数应该是相当容易理解了。一开始就是因为理解得不透彻,导致我自己犯了很低级的错误。然后,根据新的构造函数的原型,来总结一下上面的内容。
col:Store, row:Index
YES
NO
COMPRESS
NO
存储,但是不建立索引,当然也就不分析。这样的字段无法搜索,但是会出现在搜索结果中。
无意义。引发Illegal Argument Exception。
基本等同于YES,但是外加了压缩。对于较长的文本和二进制的字段,应该选用这个参数。计算量更大。
ANALYZED
分析,索引,存储。
分析,索引,不存储。
分析,索引,压缩存储。
NOT_ANALYZED
不分析,但是索引,存储。
不分析,直接索引,但是不存储。
不分析,但是索引,压缩存储。
NOT_ANALYZED_NO_NORMS
(高级)
(高级)
(高级)
ANALYZED_NO_NORMS
(高级)
(高级)
(高级)
呵呵,事实上,Lucene内部的字段种类,一共有十四种之多,像上表描述的一样。从这张表中,能发现,一个实际的,有生产能力的系统,远比理论模型要复杂,细致,考虑的细节更多。
实际上,构造的Field的时候,还有一个可选参数。就是TermVector。所谓的TermVector,字面理解就是关键字向量。通俗点说,就是实际上该字段中,被索引的词语的一个列表,还有跟每个词关联的信息,比如位置(Position,第几个词)或者偏移量(Offset,第几个字节,辅助高亮显示)。这也就暗示着一件事情,如果一个字段没有被索引,那么也不能指定其带有TermVector参数。TermVector有五种取值:
NO——默认值。不保留信息。
YES——保留词及该词出现的次数。
WITH_POSITIONS
WITH_OFFSETS
WITH_POSITIONS_OFFSETS
可以为每个Field指定TermVector选项,但是最后每个Document只维护一个Term Vector。
如果你是一个插件开发者,那么应该使用这款插件,在你发布插件的页面上放上一个Paypal Donate按钮,这样如果用户心情好了,兴许就会给你捐一点。
今天终于看到了这么款插件,但是,我这篇文章其实是想说,这个插件,我对它很不满意,对不起它灌了一个Simple的名字,插入这个按钮好麻烦啊,还要用php函数去写。
我觉得,这款插件可以有个很体贴的改进,就是增加在对shortcode的支持。这样,我不但可以在页面中插入捐赠按钮,也可以在任何一篇文章中插入捐赠按钮,你们说呢?
Options Inspector is a tool with which you can list all the options in your database, view a certain one in detail, even its data is serialized, and alter exactly a certain part of option value. It is mainly designed for plugin developers and theme designers.
dom4j是一个使用简便的开源类库,专门用来在Java平台上处理XML,XPath和XSLT,该类库使用了Java Collections Framework,完全支持DOM,SAX和JAXP。
dom4j为一个XML文档在内存中创建了一个树对象模型。 它提供了一组强大易用的API,通过XPath和XSLT来处理、操纵或者遍历XML文件,此外其中还集成了SAX、JAXP和DOM。
为了提供高度可配置的实现策略,dom4j基于接口设计。只需要提供一个DocumentFactory实现,您就可以创建您自己的XML树实现。这种设计,使得在扩展dom4j以定制您需要的特性时,能非常简单地重用dom4j的代码。
本文档将通过代码实例的方式为您提供一个dom4j的实践指南。在实验室项目中,这个开源工具包给我的工作带来了很大的便利,在这篇文章中,我将对项目中用到dom4j完成的任务做个总结,以期和互联网上的众多文档能够互相补充。