楼主: jieforest

Ruby开发技巧

[复制链接]
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
21#
 楼主| 发表于 2013-11-8 09:45 | 只看该作者
As we saw earlier with #to_ary some of these conversion methods are used internally to Ruby, these are the strict conversion methods, but can also be thought of as the implicit conversion methods. They can be used implicitly because they are strict.

These are used all over the place within Ruby, but as an example #to_int is used to convert the argument to Array#[] to an int, and #to_str is used by raise when its argument isn’t an Exception.
  1. class Line
  2.   def initialize(id)
  3.     @id = id
  4.   end
  5.   def to_int
  6.     @id
  7.   end
  8. end

  9. line = Line.new(2)
  10. names = ["Central", "Circle", "District"]
  11. names[line]   #=> "District"
  12. class Response
  13.   def initialize(status, message, body)
  14.     @status, @message, @body = status, message, body
  15.   end

  16.   def to_str
  17.     "#{@status} #{@message}"
  18.   end
  19. end

  20. res = Response.new(404, "Not Found", "")
  21. raise res   #=> RuntimeError: 404 Not Found
复制代码

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
22#
 楼主| 发表于 2013-11-8 10:15 | 只看该作者
As we saw earlier with #to_ary some of these conversion methods are used internally to Ruby, these are the strict conversion methods, but can also be thought of as the implicit conversion methods. They can be used implicitly because they are strict.

These are used all over the place within Ruby, but as an example #to_int is used to convert the argument to Array#[] to an int, and #to_str is used by raise when its argument isn’t an Exception.
  1. class Line
  2.   def initialize(id)
  3.     @id = id
  4.   end
  5.   def to_int
  6.     @id
  7.   end
  8. end

  9. line = Line.new(2)
  10. names = ["Central", "Circle", "District"]
  11. names[line]   #=> "District"
  12. class Response
  13.   def initialize(status, message, body)
  14.     @status, @message, @body = status, message, body
  15.   end

  16.   def to_str
  17.     "#{@status} #{@message}"
  18.   end
  19. end

  20. res = Response.new(404, "Not Found", "")
  21. raise res   #=> RuntimeError: 404 Not Found
复制代码

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
23#
 楼主| 发表于 2013-11-8 10:16 | 只看该作者
One final conversion method to mention is #to_proc. This is used by the unary & operator in method arguments. & converts a Proc object into a block argument in a method call.
  1. sum = Proc.new {|a, b| a + b}
  2. (1..10).inject(&sum)   #=> 55
复制代码
But first unary & will call #to_proc on its operand, this allows it to be used with objects that aren’t Procs, but can be converted to one. A very common use of this that you’ll find in modern Ruby code is using this with a Symbol.
  1. ["foo", "bar", "baz"].map(&:upcase)   #=> ["FOO", "BAR", "BAZ"]
复制代码

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
24#
 楼主| 发表于 2013-11-8 10:17 | 只看该作者
This has been built in to Ruby since version 1.9, but before that you could implement it yourself with some code like this:
  1. class Symbol
  2.   def to_proc
  3.     Proc.new do |obj|
  4.       # call the method named by this symbol on the supplied object
  5.       obj.send(self)
  6.     end
  7.   end
  8. end
复制代码

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
25#
 楼主| 发表于 2013-11-8 10:17 | 只看该作者
Here’s an example of using #to_proc and & to simplify initialising Points from an array.

  1. class Point
  2.   attr_accessor :x, :y
  3.   def initialize(x, y)
  4.     @x, @y = x, y
  5.   end

  6.   def self.to_proc
  7.     Proc.new {|ary| new(*ary)}
  8.   end
  9. end

  10. [[1, 5], [4, 2]].map(&Point)   #=> [#<Point:0x007f87e983af40 @x=1, @y=5>, #<Point:0x007f87e983ace8 @x=4, @y=2>]
复制代码

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
26#
 楼主| 发表于 2013-11-8 10:18 | 只看该作者
This is part 3 of a 5-part series on Ruby tips and tricks gleaned from our team’s pull requests over the last two years. Part 1covers blocks and ranges, and part 2 deals with destructuring and type conversions.

Exceptions

Dealing with exceptions can be tricky, and it’s easy to dig a hole for yourself that’s much harder to get out of than in. There’s a couple of rules you can follow that might be a little more work to begin with but pay off in the long run, making your code easier to debug.
First off, don’t use the statement-modifier (postfix) rescue.

Here’s an example method that’ll return nil if there’s an error fetching an item from an array.
  1. def safe_fetch(array, index)
  2.   array[index] rescue nil
  3. end
复制代码

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
27#
 楼主| 发表于 2013-11-9 10:18 | 只看该作者
However we have no idea what error it’s hiding. The array could be nil, the index nil, the index could be a string, you could have made a typo on array, or any other error you could imagine.

This means we can’t work out the intention of the code from reading it, and the result of unexpected errors doesn’t show up here, but later on in the code when something gets passed that nil. At that point it can be very hard to work out that some unexpected error here is the source of that nil.

It’s going to save a lot of hassle if you use the standard form of rescue, either with the type of error we were expecting, or some logging.
  1. def safe_fetch(array, index)
  2.   array[index]
  3. rescue NoMethodError
  4.   nil
  5. end
  6. def safe_fetch(array, index)
  7.   array[index]
  8. rescue => e
  9.   @logger.debug(e) if @logger
  10.   nil
  11. end
复制代码

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
28#
 楼主| 发表于 2013-11-9 10:23 | 只看该作者
rescue => e is a shortcut for rescue StandardError => e, just like a bare rescue is a shortcut for rescue StandardError. This means the rescue will only rescue from StandardError or subclasses of StandardError.

Generally you should follow Ruby’s example and only rescue from StandardError and its subclasses, never from Exception. Rescuing from Exception can mean you catch things you really shouldn’t, like SystemExit, the exception raised when your program has been asked to exit. If you rescue this you can end up with a program you can’t quit.

Likewise when raising errors you should only raise errors that are subclasses of StandardError, or you could end up with errors that blow though error handling they shouldn’t.

A good pattern to use in your own projects, particularly gems, is to define a general error for your whole project, and then have all others inherit from this.
  1. module MyProject
  2.   class Error < StandardError
  3.   end

  4.   class NotFoundError < Error
  5.   end

  6.   class PermissionError < Error
  7.   end
  8. end
复制代码

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
29#
 楼主| 发表于 2013-11-9 10:23 | 只看该作者
This way you can easily rescue from any error in your project with rescue MyProject::Error, or you can be more specific.

A slightly nicer way to write this, giving you an easier to read list is to take advantage of the Class.new method. Class.newgenerates a new class inheriting from the one supplied as an argument. Ruby will also name a class created like this after the first constant it is assigned to.
  1. module MyProject
  2.   Error = Class.new(StandardError)
  3.   NotFoundError = Class.new(Error)
  4.   PermissionError = Class.new(Error)
  5. end
复制代码
One problem you may face with this approach is that not all the errors generated by your code are directly raised by your code. If for example you’re making HTTP requests you’re likely to get connection errors at some point.

Now you could rescue these errors in your code, and raise new errors with types you control, but then you’re discarding valuable debugging information; the original class, message and backtrace.

使用道具 举报

回复
论坛徽章:
277
马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:112014年新春福章
日期:2014-02-18 16:41:11版主9段
日期:2012-11-25 02:21:03ITPUB年度最佳版主
日期:2014-02-19 10:05:27现任管理团队成员
日期:2011-05-07 01:45:08
30#
 楼主| 发表于 2013-11-10 10:26 | 只看该作者
A workaround for this takes advantage of Ruby allowing you to supply a class or module to rescue. rescue uses the case equality operator #=== to match its arguments against the exception. On Class this returns true if the argument is an instance of the class or instance of a subclass. On Module it returns true if the argument has the module included or extended in to it.

This means if you instead define the base error as a module, and include it into the more specific errors you get the same behaviour as before, but you can also ‘tag’ arbitrary errors raised by other bits of code you are using by extending them with the base error. These can then also be rescued by a rescue of the base error, but keep their original class, message and backtrace (you’ll no longer be able to raise the base error, but that’s ok, you should be raising the more specific errors).
  1. module MyProject
  2.   Error = Module.new

  3.   class NotFoundError < StandardError
  4.     include Error
  5.   end

  6.   class PermissionError < StandardError
  7.     include Error
  8.   end

  9.   def get(path)
  10.     response = do_get(path)
  11.     raise NotFoundError, "#{path} not found" if response.code == "404"
  12.     response.body
  13.   rescue SocketError => e
  14.     e.extend(Error)
  15.     raise e
  16.   end
  17. end

  18. ...

  19. begin
  20.   get("/example")
  21. rescue MyProject::Error => e
  22.   e   #=> MyProject::NotFoundError or SocketError
  23. end
复制代码

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表