Ruby 一小段简单的 Ruby 代码,不知道为什么会栈溢出

shaunli · 2015年03月08日 · 最后由 lithium4010 回复于 2015年03月08日 · 2394 次阅读

《七周七语言》的练习题,当前目录下有一个 RubyCSV.txt 的文件,文件内容是:

one, two
lion, tiger

写一段代码读文件,并支持如下操作:

csv = RubyCSV.new
csv.each {|row| puts row.one} # 打印one那一列

这是我的代码:

# -*- coding: utf-8 -*-
class CSVRow
  def method_missing name, *args
    puts name # <--------- 此句为了debug
    row.key?(name.to_s) ? row[name.to_s] : nil
  end

  def initialize(headers, row)
    @row = Hash[headers.zip(row)]
  end
end

module CSVEnumerate
  def each(&block)
    csv_contents.each &block
  end
end

class ActsAsCSV
  include CSVEnumerate

  def read
    file = File.new(self.class.to_s.downcase + '.txt')
    @headers = file.gets.chomp.split(', ')

    file.each do |row|
      @result << CSVRow.new(@headers, row.chomp.split(', '))
    end
  end

  def headers
    @headers
  end

  def csv_contents
    @result
  end

  def initialize
    @result = []
    read
  end
end

class RubyCSV < ActsAsCSV
end

这是测试代码:

csv = RubyCSV.new
csv.each {|row| puts row.one}

结果发现在 method_missing 处爆栈了,试着打印 name 参数(puts name 这一句),发现打印了无数条 row,说明一直在调用这个方法。可是代码中明明没有调用任何 row 方法啊?不知道是为什么。

请高手帮我看看错在哪里了。 谢谢:D

你的row在 CSVRow 里面没有定义,所以到了row.key?..那一行就转到了 method_missing, 然后又没有定义,又调用 method_missing, 所以是一个死循环。加一个 attr_accessor :row 应该可以部分解决这个具体的 bug.

但另外还有很多的问题。

  1. ActsAsSomething 这个命名惯例一般是用来做 module, 不用来做 class
  2. CSVEnumerate 多余,并且其中调用 csv_contents, 高耦合。
  3. method_missing 在条件不符合时,最好报错或者 super
  4. ActsAsCSV 里面直接用 attr_reader 或者 attr_accessor 就好了,定义变量多余。
2楼 已删除

楼上正解,主要原因 method_missing 里面应该是 @row

#1 楼 @billy #3 楼 @small_fish__

啊,果然,原来是 row 没有定义,改成@row或者加上 accessor 就没问题了。 另外非常感谢你的其他建议,以后我会注意的:D

最后贴一下正确的代码吧(代码风格什么的没改):

# -*- coding: utf-8 -*-
class CSVRow
  attr_accessor :row

  def method_missing name, *args
    row.key?(name.to_s) ? row[name.to_s] : nil
  end

  def initialize(headers, row)
    @row = Hash[headers.zip(row)]
  end
end

module CSVEnumerate
  def each(&block)
    csv_contents.each &block
  end
end

class ActsAsCSV
  include CSVEnumerate

  def read
    file = File.new(self.class.to_s.downcase + '.txt')
    @headers = file.gets.chomp.split(', ')

    file.each do |row|
      @result << CSVRow.new(@headers, row.chomp.split(', '))
    end
  end

  def headers
    @headers
  end

  def csv_contents
    @result
  end

  def initialize
    @result = []
    read
  end
end

class RubyCSV < ActsAsCSV
end

csv = RubyCSV.new
csv.each {|row| puts row.one}

怎么才能不写这个 Row 的 class 呢? 还是太罗嗦了。。。觉得需求的row.one就是不合理啊。

谢谢#6 楼 @spacewander告诉我 IO.readlines.


1 require '~/ruby_csv.rb'
2 
3 csv = RubyCSV.new('file.txt')
4 csv.each { |row| puts row.one }
 1 class RubyCSV
 2   def initialize(file_name)
 3     arr = IO.readlines(file_name).map { |line| line.split(',') } # @spacewander
 4     @row = RubyCSVRow.new(arr[0].map { |s| s.to_sym }, arr[1])  
 5   end
 6   
 7   def each; yield @row; end
 8 end
 9 
10 class RubyCSVRow
11   def initialize(arr_key, arr_value)
12     @hash = Hash[arr_key.zip(arr_value)]
13   end
14   
15   def method_missing(name, *args)
16     @hash.key?(name) ? @hash[name] : nil
17   end
18 end

#5 楼 @lithium4010

arr = IO.readlines('file_name').map do |line|
  line.split(',')
end
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册