坂野 正明です。

以下、def の引数に関しての Summary 改定版(Ver.2) を投稿します。
# jihg さん、修正点の投稿をどうもありがとうございました。
# 取り込ませて頂きました。


#-------------------- Summary (revised=Ver.2) --------------------------

[Q]
 Ruby の def 文においては、引数の受渡しは、多重代入と大体同じ
 (違いは引数の数のミスマッチに関する厳しさだけ?)。よって、
 def文の中で、破壊的メソッドを使う時は要注意。引数にとった
 オブジェクトを誤って"破壊"しないためには、どうすればいいか?

 # なお、Numeric, True/False/NilClass, Procクラスなどには破壊的
 # メソッドは存在しない(immutable: 不変)ので、気にしなくてもいい。


[A1] (正当派?)

 defの内部で(引数で渡されたオブジェクトに対して)破壊的なメソッドを
 適用しない。

 # オブジェクト指向の場合、現在の変数が指している実体のオブジェクトが何か
 # を、常に意識しながらプログラミングすることが本質的に求められている?

[A2] (名付け派?)

 (上と同じだが、誤らない工夫として)
 defの際に、引数の名前に"in_"とか"out_"とかを付ける。

[A3] (慎重派?)

 予想外のことが起こっていないか、しっかり、テストする。
 rubyunit などが有用。
 cf. http://homepage1.nifty.com/markey/ruby/rubyunit/

[A4] (lint派?)

 予想外のことが起こっていないか、外部プログラムでテストする。
 jihg さんのプログラムを、末尾 [付録1] に添付している。

[A5] (自爆派?)

 def で受け取った引数に対し、ただちに .freeze する。誤って破壊的
 メソッドを適用すると、エラーが出るので、チェックできる。
 (長所)
   ・チェックが容易。
     おそらく、エラーメッセージから直ちに問題箇所を発見できる。
 (難点)
   ・複雑な分岐があると、チェック洩れの可能性あり(これは上[A3]と同じ)。
   ・一旦、.freeze すると、その解除はできない。
   ・freeze 自体が、オブジェクトの状態を(グローバルに)変更する。
     つまり、def を呼んだルーチンの方でも当然この変更が有効になる。
     かつ、解除できないから、しばしば困ることになるだろう。
   ・どちらにせよ、グローバルな状態を変更する def 文は、汎用的には
     なりにくいので、debug が終わって本番の時には、外すべきか。

[A6] (邪道派?)

  def で 引数を受け取った直後に、他の変数(もしくは自分自身)に .clone する。

  (注意点1)
	immutable なオブジェクトは .clone が未定義(のことが多い)
	なので、要注意。

  (注意点2)
	.clone は、オブジェクト自身をコピーするのであって、オブジェクト
	が指し示す他のオブジェクトまで再帰的にコピーするわけではない。
	例えば、a=[x];b=a.clone; とした場合、a[0].equal?(b[0]) は true。
	つまり、特に Array や Hash オブジェクトに対しては、たとえ .clone
	しても、破壊的メソッドの適用は慎重に行うべし。

  これを簡単に行うためには、例えば、以下のような方法があるだろう。
  # 以下の方法では、上の注意点1は意識しなくてもよいが、注意点2は
  # 依然意識しなくてはならない。

  ○方法I
  I-1. 以下のファイル( make_clones.rb )を用意して、ライブラリパスに置く。
	#--- ここから
	def make_clones( bind, names )
	  names.gsub(/[*&]/,'').split( "[, ]+" ).each do | name |
	    eval( "#{name} = #{name}.clone rescue nil", bind )
	  end
	end
	#--- ここまで
  I-2. ruby -rmake_clones で make_clones.rb を require。
  I-3. def 文において、
	def xyz(a,b,*c)
	  make_clones( binding, "a,b,c" )
	  ...
     などとして、最初に一気に引数を(同じ名前で) .clone する
     # "*obj" などの場合は、"*"を外すこと。

  ○方法II
  II-1. 以下のファイル( clone.rb )を用意して、ライブラリパスに置く。
	#--- ここから
	def clone(*a)
	  if a.size == 1
	    a[0].clone rescue a[0]
	  else
	    a.map{|i| i.clone rescue i}
	  end
	end
	#--- ここまで
  II-2. ruby -rclone で clone.rb を require。
  II-3. def 文において、
	def xyz(a,b,*c)
	  a,b,*c=clone(a,b,*c)
	  ...
     などとして、最初に一気に引数を(同じ名前で) .clone する
     # Editor で引数部を丸ごとコピーすればいい (default値の設定がなければ)。


[感謝!]
  鈴木 さま
  まつもと ゆきひろ さま
  斉藤 秀格 さま
  中田 伸悦 さま
  新井康司 (Koji Arai) さま
  原 (信) さま
  jihg さま


[参考] (@ruby-list)
  http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/21159
	「メソッドの入り口」に始まるスレッド
    http://blade.nagaokaut.ac.jp/ruby/ruby-list/thr21063-21820.html#root_21159
  http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/21267
	「引数コピーとオブジェクト指向(Re: メソッドの入り口)」に始まるスレッド
    http://blade.nagaokaut.ac.jp/ruby/ruby-list/thr21063-21820.html#root_21267


[付録1]
  「def の引数に対して破壊的メソッドを使っていたら警告する lint風プログラム」
   (jihg さん作成)
	オリジナルは (テストサンプルも含まれる)
	http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/33921
	http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/33925
	(坂野による)修正点は
	http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/33927
	jihg さんによる修正点が (およびテストサンプルの追加も)
	http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/33938
	にあります。

+----------------- 説明

=begin
=機能

引数を破壊する操作を警告する。感度が高い。

=対象

組み込みクラス String ,Array ,Hash の破壊的メソッド。

自作クラスの破壊的メソッドには対応していない。
※課題。

=制約

関数定義は、def と end のインデントがバイト単位で同一でなければならな
い。

    def foo(arg)
        end             # 無視される
    
    arg << "break!"     # 警告

次のような形式は許されない。

    def foo(arg); ... ;end

def 〜 end を入れ子にすることは出来ない。

    def foo(arg)
        arg << "break!" # 警告
        eval <<EOS
            def bar        # ここから変わってしまう
            end         # ここで終わり
    EOS
        arg << "break!" # 警告されない
    ebd

def 〜 end 内に、バイト単位で同一のインデントを持つ end を置くことは出来
ない。

    def foo(arg)
    if flag
        true
    end                 # ここを def の終わりと認識する。
    
    arg << "break!"     # 警告されない
    end

def 〜 end の構文および対関係が適正でなければならない。

    def foo(arg         # 閉じ括弧がない
    end
    
    Def foo(arg)        # Def は間違い
    end
    
    def foo(arg)        # end が存在しない

以下は検出できない。

    def bar(a ,b)
        eval(a)
    end
    def foo(arg)
        bar("b << \"break!\"" ,arg)
    end

    def abc (a)
        b=a
        b.xxx!
    end


=参考文献

「Ruby プログラミング入門」
原信一郎 著,まつもとゆきひろ 監修,オーム社
=end

+----------------- 本体
flnm = ARGV.shift
open(flnm ,"r") do |flh|
  row = 1
  count = 0
  re = nil
  reEnd = nil
  while line = flh.gets
    line.scan(/(?:^|\s+|;)def\s+.+?\((.*?)\)/i) do |s|
      sRE = (s[0].split(/\s*,\s*/)).each do |ss|
      	next if /^\s*\&/ =~ ss
      	ss.sub!(/^\s*\*/,'')
        ss.gsub!(/(.+?)\s*(?:$|=.*)/){ $1 }
      end.join("|")
      re = /[^\w@$\.](#{sRE})
        (?:
          (?:
            \.
            (?:
              (?:
                 (?:replace)
                |(?:push|unshift|pop|shift|concat|clear|fill)
                |(?:store|delete(?:_if|_at)?|update)
              )
              \b|\w+!
            )
          )
          |(?:\s*(?:<<))
          |(?:\[.+?\]\s*=\s*)
        )
      /x
      sREEnd = line.scan(/\s*/)[0]
      reEnd = /^#{sREEnd}end\b/
    end
    unless re.nil?
      line.scan(re) do |sArg|
        print "warning : #{flnm}(#{row})---" +
          " 引数 #{sArg} に対し破壊的な操作をしています。" +
          "'#{line.scan(/(.*?)(?:\r\n|\r|\n)/)}'\n"
        count += 1
      end
      re = nil if reEnd =~ line
    end
    row += 1
  end
  puts "(#{count})count."
end

#Version 2002.2.13 -> 14?
+----------------- ここまで



[付録2]
  「immutable なオブジェクトについて」

immutable なオブジェクトかどうかは、以下のように判定できる
(immutable なオブジェクトは clone できないことを利用)。

	class Object
	  def immutable?
	    begin
	      self.clone
	      false
	    rescue TypeError
	      true	# clone できなければ、immutable
	    end
	  end
	end

ただし、1.6 では Numeric 系で上記が通用しないので、

	class Object
	  def immutable?
	    begin
	      self.equal?(self.clone)
	    rescue TypeError
	      true	# clone できなければ、immutable
	    end
	  end
	end

の方が良い。


#-------------------- Summary ここまで --------------------------