Another Java Geek

其实我不是geek,我和你一样,我是凡客

正在浏览 开发 里的文章

这是个老生常谈的话题,多数程序员对此应该都并不陌生,不过从我不太长的从业经历来看,具有良好编码风格的程序员实在是凤毛麟角。个人感受:越优秀的程序员,越具有良好的编码风格。或者换个角度说,有良好编码风格的程序员,其它方面也差不到哪儿去。正所谓:见微知著,细节决定成败,一屋不扫何以扫天下。 关于编码风格,有两本很不错的书:一本是林锐博士的《高质量C/C++编程指南》,一本是bob大叔的代码整洁之道》)。前者适合C/C++程序员,后者适合Java程序员。在我初学编程时,看了《高质量C/C++编程指南》的电子版,有意识地实践,基本上奠定了此后的编码风格。《代码整洁之道》是去年有次部门购书时报的书单之一,由于里面的很多原则早已耳熟能详,我花了两三个晚上快速扫了一遍,然后推荐给组内其它成员看。之所以谈起这个话题,并不是想表明自己的编码风格有多好,只是最近有新人入职,我首先与其强调编码风格,因此有必要将一些心得、想法加以整理,贴到博客上。 代码风格是很多编码习惯的统称,例如:命名、注释、空格/空行、缩进、对齐、括号的使用、函数/方法的设计、概念的组织、抽象层级,等等。好的风格在于一致性,拿命名来说,骆驼命名法、下划线命名法、匈牙利命名法等各有其适用范围,如果你采用某种命名法,就当在你的程序中以一贯之地使用,假如混用就是不良风格了。 在一个多人项目团队里,统一的、整洁的代码风格有很多好处: 易于阅读、理解和调试,从而减少维护成本、提高生产率; 每个人可以快速切入和接手其它人的工作,在人员更替时减少交接成本; 提高团队成员之间的舒适感和信任感,大家写出的代码如出一辙,相互更容易悦纳对方,并且增强信任感; 高质量的代码,加上前述的好处,将提升产品的稳定性和团队的执行力。 计算机教育要从娃娃抓起,编码风格要从新人抓起 不少人认为编码风格无关紧要(特别是项目经验、工程经验较少的人),不注意培养编码风格,编码完全意识流,写哪算哪,只要运行结果正确就完事,一旦定型,很难改变,随着你的工作年限和资历的增长,不会有人监督你,不经意间,造成很高的维护成本,影响自身成长,于人于已带来诸多不便。良好的编码风格,需要一开始有意识地训练一段时间,可能是几周几月甚至更长时间,但最终会从中受益,良好的编码风格有助于提高效率,从而将时间精力解放出来,去做更大范围的或更有意义的事情。

在Thrift的Java库中,读写字符串的编码固定为UTF-8,要想以其它编码(如GBK)来传输字符串不是很灵活(在C/C++中不存在这个问题,因为C/C++中的字符串无非是字节序列)。例如,java版的TCompactProtocol中的readString和writeString代码如下: public String readString() throws TException { int length = readVarint32(); if (length == 0) { return “”; } try { if (trans_.getBytesRemainingInBuffer() >= length) { String str = new String(trans_.getBuffer(), trans_.getBufferPosition(), length, “UTF-8″); trans_.consumeBuffer(length); return str; } else { return new String(readBinary(length), “UTF-8″); } } catch (UnsupportedEncodingException e) { throw new TException(“UTF-8 not supported!”); [...]

近期部门在做新系统的开发,子系统和子系统之间免不了需要约定各种各样的接口;我们负责的子系统内部,在进行系统设计和模块划分时,也需要定义各个模块之间的接口。由于各个子系统固有的异构性,以及参与人员的个人背景和对问题理解等方面的不同,对接口的约定难免有分歧,因此,经常会翻来覆去的讨论和修改接口。 这里有一篇关于API设计的文档,API是应用编程接口,虽然我们平时所约定的接口并不全是公开的API,但是关于API设计的原则和方法,对于普通的接口设计,同样具有很好的借鉴意义。我摘录其中的主要思想,结合在别处所见的关于接口设计的原则,并加以整理,记录于此(原文中出现的“API”,这里大多换成了“接口”,替换之后某些地方的意思可能不大通顺)。 —————————分割线————————— 好接口的几个特征 1.易于学习和记忆 2.产生易读的代码 3.难以被误用 4.易于扩展 5.完整性 注意:“最小化”和“一致性”没有出现在这个列表之中,它们已被其它特征所涵盖了。因为,臃肿的、不一致的接口,很难被学习和记忆,并且难以扩展。 “最小化”是指尽可能让模块对外只公开最少的部分。这里的“模块”可以是一个类、一个包、一个组件或一个子系统等。类应尽可能拥有最少的public方法或成员;包尽可能拥有最少的公开类;组件尽可能拥有最少的配置参数;子系统对外提供尽可能少的公开接口,等等。 “一致性”等同于“概念完整性”,要让一个复杂系统保持架构师愿景中的条理清晰、前后一贯的设计,必须遵循“概念完整性”原则。《人月神话》及《设计原本》的作者Frederick Brooks曾写道:“概念完整性是最重要的系统设计原则。一个省略了某些不协调的功能特性、但保持一组合理设计意图的系统,要远胜于一个包含很多初衷良好、但相互独立且不协调的意图的系统”。 1.易于学习和记忆 易于学习的接口应当具有一致的命名规范和模式,为相同的概念使用相同的名字,为不同的概念使用不同的名字。 最小化的接口容易记忆,因为需要记住的东西也少。一致性的接口容易记忆,因为你可以在一个地方应用先前在其它地方已经学会的规则。 接口并不仅仅是类和方法的名字,而且还包含它们的语义。接口的语义应尽可能的简单、清晰,并且遵循最小惊奇原则(the principle of least surprise),让常见的任务简单易行;不常见的工作可行,但不会让用户过分关注;解决特殊问题时,不要让解决方案不必要的过度通用。 2.产生易读的代码 程序代码一经写就,会被不同的开发者读(并且除错和修改)多次。易读的代码可能需要花点时间来写,但是可以节省产品周期中的其他时间。易读的代码容易文档化和维护,而且不大可能包含bug,因为易读性让bug无处遁形。 易读的代码可能简洁也可能啰嗦,不管哪种情况,它总是处在恰当的抽象层级上——既不隐藏重要的事情,也不强制程序员指定不相关的信息。 3.难以被误用 经过良好设计的接口,更容易编写出正确的代码,而且会促进好的编程实践。它不会强制使用者按照严格的顺序调用方法、注意隐含的副作用或奇异的语义。 4.易于扩展 接口会随时间变化,有可能向已有模块添加新类、向已有类添加新的方法、向已有方法中添加新的参数、增加新的枚举值等,设计接口时应当将此谨记于心。接口越复杂,在新概念和已有概念之间越有可能发生冲突。此外,在接口初始设计阶段就应该考虑二进制兼容性。 5.完整性 理想情况下,接口应当是完整的,允许使用者完成任何他们想做的事情。完整的接口并不是要提供所有的功能,但至少可以允许用户扩展或自定义现有接口。 完整性会随时间而变化,随着时间的推移,可能会有新功能逐步添加到已有接口之中。 接口设计的流程 1.仔细研究需求 咨询他人,看看别人想要什么样的功能。 2.设计之前先写用例 一个很常见的错误——编写组件库时,先实现功能,再设计接口,然后发布。这种方式设计的接口,会反映出底层代码的结构,而不是调用者愿意看到的样子。 3.看看有没有现存的相似接口 这样有可能从现有相似接口的设计中学习有用的东西,避免重蹈覆辙,复用已有的概念,并保持一致性。 4.先设计,后实现 首先定义接口和它的语义,不要担心实现的复杂度。一个接口直观但实现复杂且布满trick的库,要优于一个实现简洁但接口复杂的库。 5.找人帮你评测接口 寻求反馈,特别是负面的。这些意见更能帮助你改进设计。 6.使用你的接口编写示例 这有助于你发现潜在的问题,而且可以帮助别人了解如何使用你的接口。 7.做好扩展的准备 这要求你了解现实的场景,并思考可能的情况。 8.内部API没评测之前不要发布 有些API一开始是内部使用的,后来大家觉得很有用,才公开发布。一个常见的错误就是发布之前没有进行完整的测试。 9.宁缺毋滥 如果对接口的功能不是很确定,先不要发布。 接口设计原则 1.命名 名字要能解释自己,要遵从英语语法。千万别一会英文单词,一会汉语拼音。 参数的命名也要清楚明白。尽量少用bool类型的参数,这样的代码不好读。 命名要统一。不要混用类似widget和control这类词语,这会让用户乱猜。参数的顺序也要一致。 [...]

Thrift作为一个跨语言的RPC开发框架,其强大的代码生成能力使得开发网络服务变得异常简单。它使用由IDL(接口定义语言)编写的描述文件来定义数据结构和接口,然后通过IDL编译器(即代码生成器)来生成目标语言(如Java)代码,包括服务端和客户端代码。将生成的代码以及运行时依赖库拷贝/引入到开发项目中,即可直接调用。这大体上就是thrift的基本用法。 在实际开发中,一个大型系统往往包含很多子系统或模块,对应很多的代码项目。子系统与子系统之间、模块与模块之间有交互,这就意味着接口会非常多。各个代码项目有相对独立的开发进度,会分别演进。采用thrift作为子系统间接口的实现技术时,需要考虑几个问题: 1.IDL文件以及生成的代码如何管理?集中还是分散? 2.客户端和服务端通常位于不同的代码项目,由不同人维护,谁负责修改接口?如何保持接口的版本同步? 3.从IDL生成的代码本身也是源代码,是否允许修改? 4.从IDL文件需要两步编译才能生成二进制代码,普通源代码只需一步编译,编译构建工具如何支持? 5.如何控制thrift对正常业务代码的侵入性?当有更好的替代技术时,能否轻易替换掉thrift? 对于上述问题,我不知道其它公司(如facebook)在使用thrift时,采取了哪些策略,说说在我们项目中应用thrift的一些想法和实践。 1.IDL文件是子系统间交互的接口定义。采用集中管理的方式,有利于对整个系统的行为有一个宏观的把控,但由于各个子系统之间可能经常变化接口,使得集中管理的工作量比较大,而且为每一个使用thrift接口的代码项目引入了一个外部依赖。目前项目中通过分散方式来管理thrift接口,哪里使用哪里管理。 2.当一个接口的客户端和服务端分属不同项目时,同样应当根据客户-服务角色,在服务端项目中维护IDL并生成代码,服务端会把生成的代码编译成二进制库,客户端单向地从服务端获取接口实现的二进制库。 3.要改变接口时,决不应该直接去修改生成的代码,而是先修改IDL源文件,然后重新生成。我们的项目中,为IDL以及生成的代码单独建了一个目录,放在普通代码src目录之外,并在说明文件中明确声明这一点。 4.我们的Java项目使用Ant构建工具,在Ant脚本中,添加一个compile-idl构建目标,然后为普通的compile引入依赖关系。compile-idl代码如下: <target name=”compile-idl” depends=”init” description=”Compile IDL source files”> <apply executable=”${thrift.exec}” parallel=”false” failonerror=”yes”> <arg value=”–gen”/> <arg value=”java”/> <arg value=”-o”/> <arg value=”${thrift.output.dir}”/> <srcfile/> <fileset dir=”${thrift.idl.dir}” includes=”*.thrift”/> </apply> </target> <target name=”compile” depends=”compile-idl” description=”Compile java source files”> … </target> 5.在thrift之上构建一个隔离层,对正常业务代码屏蔽底层通信接口的实现。如果将来替换thrift,只需要修改隔离层代码。

原文出处:http://www.multicians.org/thvv/proverbs.html .contleft{margin:5px;width:48%; float:left;border-right:#999 1px solid;} .contright{margin:5px;width:48%; float:left; } hr {margin-top:7px;*margin: 0; border: 0; color: #999; background-color: #999; height: 1px; clear:both; } Software Engineering Proverbs collected by Tom Van Vleck 软件工程箴言 由Tom Van Vleck收集整理 A clever person solves a problem. A wise person avoids it. – Einstein 聪明人解决问题。智者避免问题。 – 爱因斯坦 André Bensoussan once explained to me [...]

NodeJS是时下方兴未艾的服务器端Javascript技术,基于google的V8引擎,特点是事件驱动、非阻塞IO、高性能。 在最近一个项目中,我需要对生产环境的性能状况进行实时监测且能可视化。出于性能方面的考虑,这个监测系统与生产系统弱耦合,生产系统定时将性能数据通过RPC方式推送给监测系统。由于Java的开发方式实在比较笨重,在初始阶段,NodeJS进入了我的视线,刚好在howtonode上看到一篇Realtime Performance Visualizations using Node.js,大受鼓舞,遂决定采用NodeJS来开发。可生产系统是Java应用,这就需要在Java和NodeJS两种异构系统间互操作,于是Thrift派上了用场。 基本的方法是用Thrift定义一个RPC服务,然后分别生成NodeJS和Java代码,生产系统调用Java客户端,监测系统使用Javascript服务端,进行RPC通信。 NodeJS与Thrift的使用步骤: 1.安装Thrift(参见这里) 2.安装NodeJS(过程略) 3.安装npm(经典的一条命令搞定) 4.安装thrift协议Node实现 npm install thrift 5.编写.thrift文件定义RPC服务 6.生成nodejs(和java)代码 thrift –gen js:node –gen java myservice.thrift 7.代码已经生成好了,想怎么玩就怎么玩了… 问题说明 通过npm安装的这个thrift协议实现,目前只有BinaryProtocol。在读取字符串时发现中文乱码,翻看源码后找到了问题所在: 源码文件 vi /usr/local/lib/node/.npm/thrift/0.6.0-1/package/lib/thrift/protocol.js 其中readString和writeString定义如下: TBinaryProtocol.prototype.writeString = function(str) { this.writeI32(str.length); this.trans.write(str); } TBinaryProtocol.prototype.readString = function() { var r = this.readBinary().toString(‘binary’); return r; } 这两个函数都假定读写的是ascii字符串。 为了支持中文,改写一下函数定义,使用utf8编码即可: TBinaryProtocol.prototype.writeString = function(str) { var [...]

当Ant tar找不到输入文件时,会报出”Nothing to do: <tarball-name> is up to date”的错误提示,不得不说,这个消息非常具有误导性,参见这里。特别是当某次更改build.xml之后,不幸报出这个消息,可得注意,免得花冤枉时间去检查诸如文件修改时间之类的疑似问题。遇到这个错误,基本上是由于tar任务本身有问题。 错误示例: <property name=”build.dir” value=”build” /> <property name=”build.config.dir” value=”${build.dir}/config” /> <property name=”build.lib.dir” value=”${build.dir}/lib” /> …….. <tar destfile=”${dist.dir}/${name}.tar.gz” compression=”gzip”> <tarfileset dir=”${build.dir}“> <include name=”${build.config.dir}/**” /> <include name=”${build.lib.dir}/**” /> </tarfileset> </tar> 正解: <property name=”build.dir” value=”build” /> …….. <tar destfile=”${dist.dir}/${name}.tar.gz” compression=”gzip”> <tarfileset dir=”${build.dir}“> <include name=”config/**” /> <include name=”lib/**” /> </tarfileset> </tar>

数据类型 protobuf thrift protobuf thrift protobuf thrift protobuf thrift double double float byte i16 int32 i32 int64 i64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool bool string string bytes binary message struct enum enum service service 综合对比 protobuf thrift 功能特性 主要是一种序列化机制 提供了全套RPC解决方案,包括序列化机制、传输层、并发处理框架等 支持语言 C++/Java/Python C++, Java, Python, Ruby, Perl, PHP, C#, Erlang, Haskell [...]

跟protobuf很相似,但其功能特性要比protobuf丰富的多。不仅可以从描述文件自动生成序列化/反序列化代码,而且可以产生完整的RPC通信框架的实现。支持的语言有很多种:C++, Java, Python, Ruby, Perl, PHP, C#, Erlang, Haskell等。 工具包安装过程 它要在linux环境下安装,需要依赖g++,boost,lex,yacc等工具库。对于不同的目标语言,它还需要依赖其它相应的工具或库。例如Java需要这些: # Apache Ant # Apache Ivy # Apache Commons Lang # SLF4J 默认情况下,它会安装好多种目标语言的生成器。如果你机器碰巧缺少某种语言依赖的库,很可能会安装失败,比如我只需要安装Java、C++、Python、Ruby这4种语言的生成器,在执行configure时,就要把其它语言都禁掉,否则会失败。 ./configure –without-csharp –without-erlang –without-perl –without-php –without-php_extension –without-haskell 然后一路make即可。 .thrift文件描述语法 struct SearchRequest { 1 : required string query, 2 : optional i32 p = 1, 3 : optional i32 n, } enum [...]

protocol buffer使用一套描述语法定义结构化数据,并通过代码生成器自动产生多种编程语言的类型定义及序列化/反序列代码,此外它还可以定义RPC服务并产生相应的接口。google官方支持的语言有三种:C++,Java,Python,通过第三方扩展可以支持更多的语言平台。 首先从安装工具包说起(网上已经说滥了,也不多我这一次),工具包主要包括一个.proto文件编译器(代码生成器)—— protoc,以及多种编程语言的运行时依赖库。 安装过程: #protoc需要c++ compiler,由于我机器没有c++编译器,第一次安装失败 apt-get install g++ #官方最新版是2.4,但下载之后解压失败,故这里用2.3 wget http://protobuf.googlecode.com/files/protobuf-2.3.0.tar.gz tar -zxf protobuf-2.3.0.tar.gz cd protobuf-2.3.0/ ./configure make make check make install 按照tutorial编译运行addressbook示例(java版),执行成功。 .proto文件描述语法 举例: message SearchResponse { enum Status { OK = 0; ERROR = 1; TIMEOUT = 2; } required Status status = 1; optional int32 total = 2[default = 0]; [...]