`

让你的代码更有ruby风格

阅读更多

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.
分享到:
评论

相关推荐

    Ruby-RuboCop是Ruby静态代码分析器基于社区Ruby风格指南

    作为一款静态代码分析器,它的主要任务是对Ruby代码进行检查,找出不符合社区广泛接受的Ruby风格指南的代码片段。Ruby风格指南是由社区成员共同维护的一系列规则,旨在提高代码可读性、可维护性和团队合作效率。 ...

    ruby源代码 ruby源代码 ruby源代码 ruby源代码5

    若要获取Ruby源代码,你可能需要访问Ruby的官方GitHub仓库或者相关开源项目,例如Ruby on Rails框架的源代码。 总的来说,理解Ruby源代码能帮助开发者更好地利用这一语言,提升编程技巧,解决复杂问题,并参与到...

    ruby实战书、代码书.rar

    1. **Ruby idioms**:书中将深入探讨Ruby的惯用法,这些是让代码更简洁、更具Ruby风格的关键。 2. **编程技巧**:如何利用Ruby的特性编写出高效且易于维护的代码,如上下文敏感的语法糖和内建函数的巧妙使用。 3. ...

    浅析Ruby的源代码布局及其编程风格

    总的来说,良好的Ruby编程风格旨在提高代码的可读性和可维护性,遵循这些规范可以让你的代码更加专业且易于理解。在实际开发中,除了关注代码的逻辑正确性,也要注重代码风格的统一和规范,这对于团队合作和长期项目...

    Airbnb Ruby代码风格指南:实战版

    基于Airbnb的Ruby代码风格指南,本资源提供了一套详尽的编程规范及示例,旨在帮助Ruby开发者提升代码质量,确保项目的可读性和可维护性。无论是个人开发者还是团队协作,遵循这些经过实战验证的指南都将使得代码更加...

    Ruby-Ruby样式指南带有linter和自动代码修复程序

    跟随社区认可的代码风格指南并使用linter,不仅能够提升代码质量,还有助于新成员更快地融入团队,因为代码风格一致,减少了学习曲线。同时,定期进行代码审查,结合linter的反馈,可以进一步提高代码的健壮性和可...

    Ruby 代码规范

    **Ruby 代码规范** Ruby是一种动态、面向对象的编程语言,以其优雅的语法和强大的元编程能力而闻名。为了确保代码的可读性、...通过持续实践和改进,开发者可以形成自己的 Ruby 代码风格,并在社区中建立良好的声誉。

    Ruby-Barkeep友好的代码评审系统

    2. **代码风格检查**:通过遵循特定的编码规范(如Ruby Style Guide),Barkeep可以帮助团队保持一致的代码风格,提高代码可读性和可维护性。 3. **复杂性度量**:Barkeep可以计算函数或方法的Cyclomatic复杂度,这...

    Ruby风格指南的最佳实践,以便现实世界的 Ruby 程序员可以编写可由其他现实世界的 Ruby 程序员维护的代码

    本 Ruby 风格指南推荐了最佳实践,以便现实世界的 Ruby 程序员可以编写可由其他现实世界的 Ruby 程序员维护的代码。一个反映现实世界使用情况的风格指南被使用,而一个坚持被它应该帮助的人拒绝的理想的风格指南则有...

    Ruby-Cane代码质量阈值检查可作为构建的一部分

    1. **代码风格**:Cane可以检查代码是否遵循Ruby编程语言的约定,例如命名约定、空格使用和注释规范等。这有助于保持代码风格的一致性,提高代码的可读性。 2. **复杂性度量**:Cane可以计算代码的圈复杂度...

    Ruby-Flay分析代码结构的相似之处

    Flay通过检查代码中的不同元素,如文字值、变量、类和方法名称,甚至包括空格、编程风格和括号使用,来评估代码的相似性。它使用一种称为“分数”的度量标准,该分数表示两个代码块之间的相似程度。分数越高,意味着...

    笨办法学ruby_笨办法学ruby_ruby_bravevk4_

    虽然本书可能不会深入到框架的学习,但它会为你打下坚实的基础,让你有能力去探索和理解更复杂的Ruby项目。 总而言之,《笨办法学Ruby》是一本适合初学者的优秀教材,通过直接、清晰的讲解和实践,让读者能够快速...

    Ruby-Yard是一款Ruby文档工具

    Ruby-Yard是一款专门为Ruby语言设计的强大文档工具,旨在帮助开发者更高效、更规范地生成和管理他们的代码文档。它的核心特性在于提供了一种简洁而强大的方式来解析Ruby代码中的注释,将这些注释转化为结构化的文档...

    Ruby-Pronto对你的变化进行快速自动代码审查

    它能与GitHub、GitLab等版本控制系统集成,这样每次有新的代码更改时,Pronto就可以自动运行检查,将结果直接推送到代码审查系统。 Pronto的主要特点包括: 1. **多工具集成**:Pronto可以与多种静态代码分析工具...

    ruby(前途大好的ruby+rains)

    Rails的“约定优于配置”(Convention over Configuration)原则减少了开发者需要编写的基础代码量,使得开发更高效。 Rails的一些核心特性包括: 1. **ActiveRecord**:这是Rails中负责数据库交互的部分,它将...

    JS.Class 2.1发布 Ruby风格的JavaScript.zip

    《JS.Class 2.1:Ruby风格的JavaScript详解》 在JavaScript的世界里,开发者们一直在寻找更为优雅、灵活的编程模式。JS.Class 2.1的发布,正是为了解决这一需求,它引入了Ruby语言的类定义方式,使得JavaScript的...

    Beginning Ruby on rails 源代码

    2. **Convention over Configuration(约定优于配置)**:Rails有一套默认的约定,如文件结构、命名规范等,减少了大量配置工作,使得开发者能更快地专注于业务逻辑。 3. **ActiveRecord**:这是Rails中的ORM(对象...

    Programming ruby.pdf

    Lambda表达式提供了一种更严格的块定义方式,类似于函数,这在编写函数式风格的代码时特别有用。 《Programming Ruby》还深入探讨了Ruby的库和框架,如ActiveRecord(用于数据库操作)、Test::Unit(单元测试框架)...

    Ruby教程 脚本语言

    1. **语法风格**:Python倾向于更严格的缩进规则,而Ruby的语法相对自由,更接近自然语言。 2. **面向对象**:Ruby的面向对象更为彻底,几乎所有的元素都是对象,而Python虽然也支持面向对象,但在某些方面如全局...

Global site tag (gtag.js) - Google Analytics