注册会员 登录
Project1 返回首页

zhangbanxian的个人空间 https://rpg.blue/?79298 [收藏] [复制] [分享] [RSS]

日志

半仙的ruby研究之旅(二)万恶的undefined method "xxx" for nil:NilClass ... ... ...

已有 513 次阅读2012-12-29 21:49 |个人分类:ruby研究| , undefined, method

   不管你是一个rm新手抑或是rm老手,我相信你一定会看到过这个出错信息。是的,undefined method这个出错信息,似乎就是脚本错误的永恒主题...
   说到undefined method就得说说ruby的duck type理念,“一个东西,它走路像鸭子,叫声也像鸭子,那么它就是鸭子”,为什么都是undefined method错误,因为ruby不在乎走路的这个东西是不是鸭子,他只在乎这个东西会不会走路,如果会,就让他走路;如果不会,就抛出undefined method错误...这就是ruby执行语句的机制...
   这是一种很棒的机制,可以轻松地实现所谓的多态...
   class Worcker
     def work
       print "working"
     end
   end
   class Fucker
     def work
       print "fucking"
     end
   end
   def let_sb_work(sb)
     sb.work
   end
   let_sb_work(Worcker.new) #=>working
   let_sb_work(Fucker.new)  #=>fucking
   很简洁不是吗?回到我们的话题:
   undefined method "xxx" for nil的根本原因是什么?我们还是结合实例来看看吧...这是我以前解决的问题的一部分...
   class Sprite_Battler < Sprite_Base
     def collapse_end
       @collapse_done = true
       return_set(@original_set)
       self.src_rect.y = -self.bitmap.height unless @battler.motion_collapse[0] == 6 #出错语句
     end 
   end
   出错的信息是undefined method "height" for nil...不熟悉ruby的人(确切地说是不熟悉oo编程的人)会从字面去理解是height方法没有定义,然后去检查def height的问题,答案当然是找不到,但他也并不知道这个nil是什么意思,然后就只有放弃这个脚本;即便是熟悉ruby的人,看到这个信息就能判断出一定是这个Sprite_Battler#bitmap没有赋初值就被引用了(因为ruby的变量是即用即生成的,不需要额外声明,所以也常常会有忘记赋初值的现象产生,如果未赋值就被调用,则变量自动指向nil),但也会不知所措(当然这个问题因为这是RGSS标准库中的方法,其命名方式很规范,让人很容易就联想到Bitmap对象,其实一个良好的代码规范可以解决很多问题,处处用fucker不是一个好的习惯,尤其是对于团队作业,嗯,为什么我用了?别忘了,这只是一个演示xd),height到底是指Bitmap呢,还是指Window?我们只有通过搜索Sprite_Battler#bitmap的赋值状况来判断了...
   很好,我们确认了这是一个Bitmap的实例,那我们在出错语句之上给它赋初值加上一句self.bitmap ||= Bitmap.new(32,32),当然本例还可以有一个更优雅的解决方式那就是在出错语句之上加上一句return if self.bitmap.nil?,但这并不是通用的,本例中如果self.bitmap未赋值那后面的语句根本就没有执行的意义,但有时却并非如此,忘记赋初值很可能会造成一系列的链式反应...有人会说,忘记赋初值这种低级错误,一般都是因为脚本新手对默认脚本不熟造成的,其实不然,有时还真说不定就是脚本冲突的关系,$scene::main的结构的改变很有可能就会造成赋值的时机发生改变产生冲突...
   但是更荒唐的是这种脚本,以下是多动画脚本的一段:
      return if animation == nil
      num = @_animation.size
      @_animation.push([animation, hit, animation.frame_max, []])
   原本用来存放RPG::Sprite的@_animation直接被改成数组了,还是多维数组- -b,但更见鬼的是还有这种脚本:
 module RPG
  class Sprite < ::Sprite
    def anima?(id = nil)
      (x = (@_loop_animation ||  @_animation)) && (x.id == id || id==nil)
    end
  end
 end
    发现什么问题了吗,没错,每一个object都有id这个方法,当@_animation变成数组时,x.id返回的却仍旧是一个合法的数字,简直天衣无缝,系统并不知道自己做错了什么,它没有报错,于是脚本就莫名其妙地失效了,是的,有时不报错比报错更可怕...
    在没有脚本规范的前提下,你永远不知道别人会对脚本做哪些奇怪的修改,要想避免冲突,只有别人想到的你都得想到,别人想不到的你还得想到:
 module RPG
  class Sprite < ::Sprite
    def anima?(id = nil)
       x = (@_loop_animation ||  @_animation)
       unless x.is_a?(RPG::Sprite)
         raise TypeError , "it's #{x.inspect} but not a RPG::Sprite"
       end
       x.id == id || id==nil
    end
  end
 end   
    嗯,这样做,问题就很直观了,就算哪个混蛋乱改数据类型也不怕了。什么?这样做太麻烦了?是啊,每个变量都做一下检测,这得增加多少工作量,而且也把代码无限地加长了...确实这样做很愚蠢,ruby的理念也不是这样的,没错,不要把别人都想成hacker,他们只是一个和你一样的脚本员而已,不是么...
    虽然我们说报错是幸运的,但其实,有时错误并不严重,我们可以尝试去忽略它,嘿,我想到了一个不错的点子:
   class Object
    def method_missing(name,*args)
    end
   end
    ruby有一个实用的回调方法method_missing,在一个对象的方法未定义时会去找这个方法,如果存在,就执行该方法,而不是抛出undefined method异常,这样就可以忽略掉一切的undefined method异常了,还记得上次说的原型编程么?
    def method_missing(name,*args)
      define_singleton_method(name){|*args| fucker.send(name,*args)}
      send(name,*args)
    end
   我们甚至可以像这样,可以即时生成需要的方法,更夸张一点的就是直接去ObjectSpace去寻找一切respond_to?(name)的对象...同样我们也能指定特殊的class甚至是特殊的object享有这即便未定义方法也能继续运行的特权...你或许会说这样做的话,错误检查就更难以执行了,其实不然,只要有一套良好的log系统,鱼和熊掌完全是可以兼得的...
def method_missing(name,*args)
  return unless $TEST||$DEBUG
  fucker = open("log.txt","a+")
  msg = "#{Time.now.to_s}\nerror:\nundefined method #{name.to_s} for #{self.inspect}\n"
  if args.size > 0
  msg += "args:\n"
  args.each{|i| msg += i.inspect+"\n"}
  end
  msg += "backtrace:\n"
  caller.each{|i| msg += i+"\n"}
  fucker.write(msg)
  fucker.close
end
   怎么样?测试剧情时,却莫名其妙弹框的尴尬是不是消失了;测试完剧情。就去翻翻log,顺便解决bug,其实这远远比你改一段测试一段效率高得多,比如,一个Game实例往往会涉及到多处调用,如果因为其结构被修改往往会导致多处冲突,一般的排查者并不能猜到哪些地方会引用到,这就往往导致重复测试,而边玩边测试则是在不经意间完成了你需要多次测试才能得出的所有冲突的部分。再者有时看似完美的代码也往往会有一定的漏洞,连windows系统都要天天打补丁,你又如何保证你的代码是完美无缺的呢?游戏的崩溃,往往会直接导致玩家对此游戏失去兴趣...如果你要做一个大型游戏,就更不可能做到面面俱到的测试...我的建议是让玩家成为你的测试员,一些细枝末节的错误,玩家可能并不在意;提供一个良好的报错和补丁系统,让注意到你游戏的细节的发烧友能够及时对游戏错误进行反馈,使你能够更好地找到错误,并发布补丁,使你的游戏趋于完美...
   嗯,宁可相信玩家愿意玩一个满是漏洞的游戏,也不要相信自己能写出一个完美的游戏,这就是我要说的...

鸡蛋

鲜花

评论 (0 个评论)

facelist doodle 涂鸦笔

您需要登录后才可以评论 登录 | 注册会员

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-5-3 10:27

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

返回顶部