プログラミング入門者向けのサンプルコードで異なる名前を付けられるはずの複数の変数に同じ変数名を付けてしまう問題

Perl入学式の受講生だった5年前にも同じようなことを言っていたので変わってないな自分・・と思うとともに、この問題じたいがある種普遍的なのかな、という気も。

以下、掲題の件について簡単にまとめてみたいと思います。

Rubyで言うと、たとえばこんなサンプルコード。

class Foo
  def initialize(name:, price:)
    @name = name
    @price = price
  end

  def show
    return "#{@name}: #{@price}"
  end
end

bar = Foo.new(name: "apple", price: 100)
puts bar.show

結果は以下。

apple: 100円

引数としてクラスの外部から内部へ値を受け渡すためのname, priceと、クラスの内部でその値を操作するインスタンス変数のname, priceがそれぞれ別の変数であるにもかかわらず同名で記載・使用されています。

こういったことはPerlの入門者向けサンプルコードでも起こりがちで、なにしろPerlではスカラー変数でも配列変数でもハッシュ変数でも、それぞれ別の変数なのに同じ単語を使えるので、たとえばこんなことになります。

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';

my $apple = 100;
my @apple = (101, 201);
my %apple = (foo => 102, bar => 201);

say $apple;
say $apple[0];
say $apple{foo};

結果は以下ですが、

100
101
102

問題は、この中の

say $apple;
say $apple[0];
say $apple{foo};

と並んでる3つの「$apple」は全部別物だということですね。まったく相互の関係はありません。

これ、混乱しませんか??

ぼくは初心者のときにこう思いました。「なんでわざわざ同じ名前付けるの!?」

上記の例で関係がある(同じ変数名でなければいけない)のは、以下の組み合わせです。

# 配列とその要素

@apple
$apple[0];
# ハッシュとその要素

%apple
$apple{foo};

同じ変数名を使うなら、こうした「同じ変数名でなければいけない組み合わせ」に限るべきで、なぜわざわざ配列とハッシュを同じ変数名にするのだ・・とただ苦しむばかりでした。

まあ実際には、ぼくもやがて初心者を卒業というか、初心者であることに飽き始めた頃、「なるほど、コードを書くときはこんな風にいろいろ揃えてしまった方がラクに感じることもあるのだな」と理解するようになりましたが、とはいえ、初心者にこれは酷だろうという気持ちには今も変わりはありません。

では、そのPerlのコード、どう直したら読みやすくなるでしょうか?
一例ですが、こんな風にすればどうかと思います。

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';

my $apple = 100;
my @orange = (101, 201);
my %lemon = (foo => 102, bar => 201);

say $apple;
say $orange[0];
say $lemon{foo};

これだったら、どの変数がどこに動いているのか。それぞれの代入した値がその後にどこで使われているのか、追いやすくなります。

ようは、「同じ変数名でなければいけないものだけ」を同じ変数名にするということです。

コードには色が付いていません*1。そのようなとき、色分けをする代わりに適切な変数名*2を付ける必要があるのではないか、と思うわけです。

上記を踏まえて、あらためて最初のRubyの例に戻りますが(再掲)

class Foo
  def initialize(name:, price:)
    @name = name
    @price = price
  end

  def show
    return "#{@name}: #{@price}"
  end
end

bar = Foo.new(name: "apple", price: 100)
puts bar.show

これ、たとえば以下のようにすると、どの値がどの変数を通してどこへ動いているのか、わかりやすくなると思います。

class Foo
  def initialize(name_arg:, price_arg:)
    @name_var = name_arg
    @price_var = price_arg
  end

  def show
    return "#{@name_var}: #{@price_var}"
  end
end

bar = Foo.new(name_arg: "apple", price_arg: 100)
puts bar.show

この問題、ようは読み手のトレースする力の程度に合わせる、ということなのだと思います。コードを書いているプログラマー自身は、どの変数がどの値を抱えているのか、頭の中ですでに追えている状態なので、変数名によって値の経路を示す必要はもうなくて、むしろ別の理由、たとえば異なる中身を持った変数同士が同種の属性や役割を背負っていることを示すために、同じ変数名を付けてカテゴライズしておきたい、みたいな考えがあるのかもしれません。

しかし入門段階の人は、普通はそういったトレースをする力が十分にはないがゆえに、変数の名前からその中身の値を想像しようとしますから、異なる値・異なる変数であるにもかかわらず同じ名前がついていると、混乱が生じやすいということではないかなと。

このあたりの問題を認識した上で、その状況に応じてどうするべきなのか(異なる中身を持つ変数同士に同じ名前を付けるのか、それをしないのか)を考えられると、より良いかたちで書き手と読み手のコミュニケーションが成立しやすくなるのではないか、と思っています。

*1:エディタ等のシンタクスハイライトで色を付けることはできますが、それは個々の設定によるので、結局書き手が見ているのと同じ色付けを別の読み手が見ることはできません。

*2:ここで言う「適切さ」とはそのコードが読み書きされる背景によって変わるものだと思います。プログラマーの間だけで読まれる前提ならば、上記のような配慮は不要かもしれません。ここではあくまで「入門者にとって適切な」変数名について書いています。