- 浏览: 2072527 次
- 性别:
- 来自: NYC
文章分类
- 全部博客 (628)
- Linux (53)
- RubyOnRails (294)
- HTML (8)
- 手册指南 (5)
- Mysql (14)
- PHP (3)
- Rails 汇总 (13)
- 读书 (22)
- plugin 插件介绍与应用 (12)
- Flex (2)
- Ruby技巧 (7)
- Gem包介绍 (1)
- javascript Jquery ext prototype (21)
- IT生活 (6)
- 小工具 (4)
- PHP 部署 drupal (1)
- javascript Jquery sort plugin 插件 (2)
- iphone siri ios (1)
- Ruby On Rails (106)
- 编程概念 (1)
- Unit Test (4)
- Ruby 1.9 (24)
- rake (1)
- Postgresql (6)
- ruby (5)
- respond_to? (1)
- method_missing (1)
- git (8)
- Rspec (1)
- ios (1)
- jquery (1)
- Sinatra (1)
最新评论
-
dadadada2x:
user模型里加上 protected def email ...
流行的权限管理 gem devise的定制 -
Sev7en_jun:
shrekting 写道var pattern = /^(0| ...
强悍的ip格式 正则表达式验证 -
jiasanshou:
好文章!!!
RPM包rpmbuild SPEC文件深度说明 -
寻得乐中乐:
link_to其实就是个a标签,使用css控制,添加一个参数: ...
Rails在link_to中加参数 -
aiafei0001:
完全看不懂,不知所然.能表达清楚一点?
"$ is not defined" 的问题怎么办
Quack Attack: Making Your Code More Rubyish
2009-06-09 13:00, written by Gregory Brown
I’ve been doing some FFI work recently, which means that I’ve needed to wrap underlying C libraries so that I can call them from Ruby. While I won’t get into how the low level wrappers work, I can show you what the raw API calls look like for just a few functions:
view plaincopy to clipboardprint?
trip = API.PCMSNewTrip(server_id) API.PCMSNumStops(trip) API.PCMSAddStop(trip, string) API.PCMSGetStop(trip, index, string_ptr, buffer_size) API.PCMSDeleteStop(trip, index) API.PCMSClearStop(trip)
All things considered, this looks pretty good for a direct wrapper on top of a C library. In fact, it’s relatively simple to mirror this to a more normalized Ruby layout. We can start by noticing that these calls are basically object oriented, focusing on the Trip object. While a Trip has other responsibilities, among them is managing a list of stops. With this in mind, we can flesh out a basic Trip object:
view plaincopy to clipboardprint?
class Trip def initialize(server_id) @server_id = server_id @pointer = API.PCMSNewTrip(@server_id) @stops = StopList.new(self) end attr_reader :stops def call(*args) API.send("PCMS#{args.first}", @pointer, *args[1..-1]) end end
The Trip#call helper removes some of the duplication for us, but it’ll be easier to see how it works in just a moment. For now, it’s worth pondering what a StopList should be.
If you look at the functions listed for dealing with stops, you’ll notice they map nicely to one of Ruby’s structures. We’re dealing with an ordered list of objects that can be added to and removed from. It can also be queried for its length, and deleted entirely. These features sure sound a lot like Ruby’s Array object, don’t they?
With this in mind, let’s do a quick experiment in interface design:
view plaincopy to clipboardprint?
class StopList include Enumerable def initialize(trip) @trip = trip end def length @trip.call :NumStops end def <<(loc) @trip.call :AddStop, loc end def [](index) ptr = FFI::MemoryPointer.new(:char, 100) @trip.call :GetStop, index, ptr, 101 ptr.read_string end def delete_at(index) @trip.call :DeleteStop, index end def each length.times do |i| yield self[i] end end def clear @trip.call :ClearStops end end
Without paying too much attention to the implementation details, let’s take a look at what behaviors this new object supports:
view plaincopy to clipboardprint?
t = Trip.new(server_id) t.stops << "New Haven, CT" t.stops << "Naugatuck, CT" t.stops << "Boston, MA" p t.stops.length #=> 3 p t.stops[2] #=> "02205 Boston, MA, Suffolk" t.stops.delete_at(1) p t.stops[1] #=> "02205 Boston, MA, Suffolk" p t.stops.map { |e| e.upcase } #=> [ "06511 NEW HAVEN, CT, NEW HAVEN", # "02205 BOSTON, MA, SUFFOLK" ] t.stops.clear p t.stops.length #=> 0
If this sort of interaction looks familiar to you, it’s because you’ve likely already done things like this thousands of times. But to make it blindingly obvious, let’s replace Trip#stops with an Array.
view plaincopy to clipboardprint?
stops = [] stops << "New Haven, CT" stops << "Naugatuck, CT" stops << "Boston, MA" p stops.length #=> 3 p stops[2] #=> "Boston, MA" stops.delete_at(1) p stops[1] #=> "Boston, MA" p stops.map { |e| e.upcase } #=> ["NEW HAVEN, CT", "BOSTON, MA"] stops.clear p stops.length #=> 0
You’ll notice that aside from lacking the location name normalization that our real code does implicitly, the key points we’ve highlighted have exactly the same behavior, using exactly the same interface. One benefit is immediately obvious after seeing this; the API for StopList doesn’t require you to learn anything new.
A more subtle gain that comes with this approach is that so long as it is restricted to the subset which StopList supports, code which expects an Array-like thing does not need to change, either.
For example, the following code will work just fine with either an Array or a StopList:
view plaincopy to clipboardprint?
def humanized(list) list.each_with_index do |e,i| puts "#{e} (#{i+1} / #{list.length})" end end
This makes things easier to test, and easier to be re-used for different purposes. Both are solid reasons for using this technique.
Of course, I’ve been glossing over a semi-major issue in the original code here, which I am sure has frustrated our more pedantic readers. The current StopList code, while quite useful, does not quack perfectly with Array. We needn’t look far for signs of divergence.
view plaincopy to clipboardprint?
p t.stops << "Chicago, IL" #=> 1 p t.stops.delete_at(2) #=> 1 p t.stops.clear #=> nil
These side-effect bearing functions are returning their C based values, which are different than what you’d expect from a Ruby Array. Luckily, each of these are easy to remedy.
In the case of the append operator (<<), we should return the StopList itself, to permit chaining:
view plaincopy to clipboardprint?
def <<(loc) @trip.call :AddStop, loc return self end
To play nice with Array, our delete_at method should return the deleted object:
view plaincopy to clipboardprint?
def delete_at(index) obj = self[index] @trip.call :DeleteStop, index return obj end
Finally, since clear may also be chained, it should return the original object as well.
view plaincopy to clipboardprint?
def clear @trip.call :ClearStops return self end
With these fixes in place, we can re-visit our previous example:
view plaincopy to clipboardprint?
p t.stops << "Chicago, IL" #=> #<Trip::StopList:0x0fcac ...> p t.stops.delete_at(2) #=> "60607 Chicago, IL, Cook" p t.stops.clear #=> #<Trip::StopList:0x0fcac ...>
There are probably some other minor details to catch, but now that our Array-ish StopList is “Good Enough For Government Work”, we have a nice stopping point. Let’s wrap things up with a little summary of things to remember.
Guidelines For Making Your Code More “Rubyish”
This is just one technique among many for improving your code, but I’d argue its a fairly important one. If you have a structure that mimics a subset of a core Ruby object’s capabilities, you can gain a lot by standardizing on a compatible interface. While sometimes the similarities end at the Enumerable and Comparable mixins, it’s reasonable to stretch things farther when it makes sense to do so. If you go that route (as we did here), there are just a few things to keep in mind:
* You don’t need to implement every last feature of a core Ruby object in order to use this technique. So many functions rely on just a handful of available methods, that it makes sense to use this technique even when your problem domain is very small.
* For the features you do implement, take care to maintain the same interface both on input and output. It’s fine to not support certain use cases, or to add extensions for new ones, but you should not diverge in the behaviors you do implement unless you have a good reason to.
* Pay close attention to the return values of side-effect inducing functions, especially the ones mentioned in this article. Many Ruby methods are designed to be chainable, and breaking that feature can create a mess.
* While this technique opens the door for using primitive objects for testing higher level functions, do not forget to adequately test the functionality of the actual objects you are implementing. Basically, make sure your code really quacks like a duck before substituting it with a duck somewhere else.
发表评论
-
Destroying a Postgres DB on Heroku
2013-04-24 10:58 928heroku pg:reset DATABASE -
VIM ctags setup ack
2012-04-17 22:13 3255reference ctags --extra=+f --e ... -
alias_method_chain方法在3.1以后的替代使用方式
2012-02-04 02:14 3288alias_method_chain() 是rails里的一个 ... -
一些快速解决的问题
2012-01-19 12:35 1470问题如下: 引用Could not open library ... -
API service 安全问题
2011-12-04 08:47 1380这是一个长期关注的课题 rest api Service的 ... -
Module方法调用好不好
2011-11-20 01:58 1344以前说,用module给class加singleton方法,和 ... -
一个ajax和rails交互的例子
2011-11-19 01:53 1903首先,这里用了一个,query信息解析的包,如下 https: ... -
Rails 返回hash给javascript
2011-11-19 01:43 2272这是一个特别的,不太正统的需求, 因为,大部分时候,ajax的 ... -
关于Rubymine
2011-11-18 23:21 2263开个帖子收集有关使用上的问题 前一段时间,看到半价就买了。想 ... -
ruby中和javascript中,动态方法的创建
2011-11-18 21:01 1234class Klass def hello(*args) ... -
textmate快捷键 汇总
2011-11-16 07:20 8138TextMate 列编辑模式 按住 Alt 键,用鼠标选择要 ... -
Ruby面试系列六,面试继续面试
2011-11-15 05:55 2018刚才受到打击了,充分报漏了自己基础不扎实,不肯向虎炮等兄弟学习 ... -
说说sharding
2011-11-13 00:53 1481这个东西一面试就有人 ... -
rails面试碎碎念
2011-11-12 23:51 1939面试继续面试 又有问ru ... -
最通常的git push reject 和non-fast forward是因为
2011-11-12 23:29 17209git push To git@github.com:use ... -
Rails 自身的many to many关系 self has_many
2011-11-12 01:43 2732简单点的 #注意外键在person上people: id ... -
Rails 3下的 in place editor edit in place
2011-11-12 01:20 945第一个版本 http://code.google.com/p ... -
Heroku 的诡异问题集合
2011-11-11 07:22 1692开个Post记录,在用heroku过程中的一些诡异问题和要注意 ... -
SCSS 和 SASS 和 HAML 和CoffeeScript
2011-11-07 07:52 12952Asset Pipeline 提供了内建 ... -
Invalid gemspec because of the date format in specification
2011-11-07 02:14 2115又是这个date format的错误。 上次出错忘了,记录下 ...
相关推荐
作为一款静态代码分析器,它的主要任务是对Ruby代码进行检查,找出不符合社区广泛接受的Ruby风格指南的代码片段。Ruby风格指南是由社区成员共同维护的一系列规则,旨在提高代码可读性、可维护性和团队合作效率。 ...
若要获取Ruby源代码,你可能需要访问Ruby的官方GitHub仓库或者相关开源项目,例如Ruby on Rails框架的源代码。 总的来说,理解Ruby源代码能帮助开发者更好地利用这一语言,提升编程技巧,解决复杂问题,并参与到...
1. **Ruby idioms**:书中将深入探讨Ruby的惯用法,这些是让代码更简洁、更具Ruby风格的关键。 2. **编程技巧**:如何利用Ruby的特性编写出高效且易于维护的代码,如上下文敏感的语法糖和内建函数的巧妙使用。 3. ...
总的来说,良好的Ruby编程风格旨在提高代码的可读性和可维护性,遵循这些规范可以让你的代码更加专业且易于理解。在实际开发中,除了关注代码的逻辑正确性,也要注重代码风格的统一和规范,这对于团队合作和长期项目...
基于Airbnb的Ruby代码风格指南,本资源提供了一套详尽的编程规范及示例,旨在帮助Ruby开发者提升代码质量,确保项目的可读性和可维护性。无论是个人开发者还是团队协作,遵循这些经过实战验证的指南都将使得代码更加...
跟随社区认可的代码风格指南并使用linter,不仅能够提升代码质量,还有助于新成员更快地融入团队,因为代码风格一致,减少了学习曲线。同时,定期进行代码审查,结合linter的反馈,可以进一步提高代码的健壮性和可...
**Ruby 代码规范** Ruby是一种动态、面向对象的编程语言,以其优雅的语法和强大的元编程能力而闻名。为了确保代码的可读性、...通过持续实践和改进,开发者可以形成自己的 Ruby 代码风格,并在社区中建立良好的声誉。
2. **代码风格检查**:通过遵循特定的编码规范(如Ruby Style Guide),Barkeep可以帮助团队保持一致的代码风格,提高代码可读性和可维护性。 3. **复杂性度量**:Barkeep可以计算函数或方法的Cyclomatic复杂度,这...
本 Ruby 风格指南推荐了最佳实践,以便现实世界的 Ruby 程序员可以编写可由其他现实世界的 Ruby 程序员维护的代码。一个反映现实世界使用情况的风格指南被使用,而一个坚持被它应该帮助的人拒绝的理想的风格指南则有...
1. **代码风格**:Cane可以检查代码是否遵循Ruby编程语言的约定,例如命名约定、空格使用和注释规范等。这有助于保持代码风格的一致性,提高代码的可读性。 2. **复杂性度量**:Cane可以计算代码的圈复杂度...
Flay通过检查代码中的不同元素,如文字值、变量、类和方法名称,甚至包括空格、编程风格和括号使用,来评估代码的相似性。它使用一种称为“分数”的度量标准,该分数表示两个代码块之间的相似程度。分数越高,意味着...
虽然本书可能不会深入到框架的学习,但它会为你打下坚实的基础,让你有能力去探索和理解更复杂的Ruby项目。 总而言之,《笨办法学Ruby》是一本适合初学者的优秀教材,通过直接、清晰的讲解和实践,让读者能够快速...
Ruby-Yard是一款专门为Ruby语言设计的强大文档工具,旨在帮助开发者更高效、更规范地生成和管理他们的代码文档。它的核心特性在于提供了一种简洁而强大的方式来解析Ruby代码中的注释,将这些注释转化为结构化的文档...
它能与GitHub、GitLab等版本控制系统集成,这样每次有新的代码更改时,Pronto就可以自动运行检查,将结果直接推送到代码审查系统。 Pronto的主要特点包括: 1. **多工具集成**:Pronto可以与多种静态代码分析工具...
Rails的“约定优于配置”(Convention over Configuration)原则减少了开发者需要编写的基础代码量,使得开发更高效。 Rails的一些核心特性包括: 1. **ActiveRecord**:这是Rails中负责数据库交互的部分,它将...
《JS.Class 2.1:Ruby风格的JavaScript详解》 在JavaScript的世界里,开发者们一直在寻找更为优雅、灵活的编程模式。JS.Class 2.1的发布,正是为了解决这一需求,它引入了Ruby语言的类定义方式,使得JavaScript的...
2. **Convention over Configuration(约定优于配置)**:Rails有一套默认的约定,如文件结构、命名规范等,减少了大量配置工作,使得开发者能更快地专注于业务逻辑。 3. **ActiveRecord**:这是Rails中的ORM(对象...
Lambda表达式提供了一种更严格的块定义方式,类似于函数,这在编写函数式风格的代码时特别有用。 《Programming Ruby》还深入探讨了Ruby的库和框架,如ActiveRecord(用于数据库操作)、Test::Unit(单元测试框架)...
1. **语法风格**:Python倾向于更严格的缩进规则,而Ruby的语法相对自由,更接近自然语言。 2. **面向对象**:Ruby的面向对象更为彻底,几乎所有的元素都是对象,而Python虽然也支持面向对象,但在某些方面如全局...