見出し画像

Retro-gaming and so on

プログラミングの例題は良問じゃなきゃダメだろ、と言う話

この記事読んでたんだけど。


そりゃ悩むだろ、とか別な意味で呆れていた。
ヒデェコードである。
いや、isam氏が書いたコードが、ではない。使ってるPythonの教科書のコード例が酷い、のである。
「え?」とかビックリしていた。
ちと待てよ。「教科書」とか言うんだったらもっと綺麗なコードを載せるべきなんじゃねーの。
と言うか、「オブジェクト指向を教える」前提としてはあまりにPoorな例じゃないだろうか。
ちょっとその「写経用」になってるコードを見てみよう。

class Student:
 ''' 生徒を表すクラス '''
 def __init__(self, identity, name, score = 0):
  ''' 初期化 '''
  self.id = identity
  self.name = name
  self.score = score

 def getId(self):
  ''' IDを取得するメソッド '''
  return self.id

 def getName(self):
  ''' 生徒名を取得するメソッド '''
  return self.name

 def setScore(self, score):
  ''' 点数を取得するメソッド '''
  self.score = score

 def getScore(self):
  ''' 点数を設定するメソッド '''
  return self.score

class CalcScore:
 ''' 点数を計算する関数 '''
 def __init__(self):
  ''' 初期化 '''
  self.students = []

 def addStudent(self, student):
  ''' 学生を追加する '''
  self.students.append(student)

 def ave(self):
  v = 0
  for i in self.students:
   v += i.getScore()
  ave_v = v / len(self.students)
  return ave_v

# 学生を表すインスタンスを生成
p1 = Student(10, "佐藤")
p1.setScore(80)
p2 = Student(11, "鈴木", score = 79)
p3 = Student(12, "佐々木", score = 84)
p4 = Student(13, "井上", score = 77)

# 平均点を計算
calc = CalcScore()
calc.addStudent(p1)
calc.addStudent(p2)
calc.addStudent(p3)
calc.addStudent(p4)
print("平均点 = ", calc.ave())

いやこりゃひでぇだろ。
何がダメなんだろうか。それはこれはPythonのコードではなくまるでJavaのコードだから、だ。
そこには2言語の違いが何なのか、と言う把握がなく、ただ単に

「Javaが正しいんだからPythonでもJavaのように書けば正しいオブジェクト指向になる」

と言う無邪気な信頼がある。
んなワケねぇのだ。
Pythonでのオブジェクト指向で個人的な実験ならいざ知らず、こんなサンプルを出されたら怒って良いと思う。いや、マジで。

ちょっと見ていこう。
まずはこの部分だ。

class Student:
 ''' 生徒を表すクラス '''
 def __init__(self, identity, name, score = 0):
  ''' 初期化 '''
  self.id = identity
  self.name = name
  self.score = score

 def getId(self):
  ''' IDを取得するメソッド '''
  return self.id

 def getName(self):
  ''' 生徒名を取得するメソッド '''
  return self.name

 def setScore(self, score):
  ''' 点数を設定するメソッド '''
  self.score = score

 def getScore(self):
  ''' 点数を取得するメソッド '''
  return self.score

Javaで言うトコのgettersetterの嵐である。
しかしそんなん必要か?マジで?
まず、Javaならこう書きたい人がいる、って事がいるのは知っている。そしてJavaだと次のように書くのが望ましい、たぁ言われてるよな。多分。

public class Student {
 // 生徒を表すクラス
 private int id;
 private String name;
 private int score;
 public int getId() {
  // IDを取得するメソッド
  return this.id;
 }
 public String getName() {
  // 生徒名を取得するメソッド
  return this.name;
 }
 public void setScore(int score) {
  // 点数を設定するメソッド
  this.score = score;
 }
 public int getScore() {
  // 点数を取得するメソッド
  return this.score;
 }
 Student(int id, String name) {
  // 初期化
  this.id = id;
  this.name = name;
  this.score = 0;
 }
 Student(int id, String name, int score) {
  // 初期化
  this.id = id;
  this.name = name;
  this.score = score;
 }
}

あまりJava書くのは自信がないんだけど、エラーを喰らわなかったんで、多分こんなカンジだろう。
んじゃあ、どうしてJavaではこれでO.K.なのにPythonだとダメなのか。
まずは単純に言うとJavaにはアクセス修飾子があるけどPythonには存在しない。つまり、privateを使えるJavaならgettersetterが意味があってもPythonだとそうじゃない。Pythonだとgettersetterを作っても、結局インスタンス変数に直接アクセス出来てしまうので、無意味なのだ。
そして、もっと言うと、Javaは「オブジェクト指向を強要する」。Pythonはそうじゃないのだ。
gettersetterを外して考えてみろ。PythonもJavaもgettersetter以外のメソッドを持たない。と言う事は事実上この部分のコードはC言語で言う構造体の役目しか果たしてないのだ。
だったら構造体としての役目だけでいいのだ。しかしもう一回繰り返そう。Javaはオブジェクト指向を強要するしかしPythonはそうじゃないのだ
と言うことはPythonの場合はたとえクラスだろうとマトモなメソッドが無い以上、Studentを構造体として扱えば良い、ってだけの話となるオブジェクト指向の例としてはあまりにもお粗末なのだ。PythonではJavaのような強制がそもそも生じないのである。
んでな。個人的な意見を言うけど。
getterとかsetterってぶっちゃけくだんねぇんだよ。いわゆるコーディング規約の一種だろ?でもな、本当に必要ならログラミング言語自体がそれらを自動生成すべきなの。そうなってない、ってのは必須要素ではない、って事なんだわ。分かる?
例えばANSI Common Lispだと構造体作る時、defstructで作るわけなんだけど。

CL-USER> (defstruct Student
      id
      name
      (score 0))
STUDENT
CL-USER>

もうこの時点でJavaでのgettersetterに当たるアクセサは自動で定義される。プログラマ側がわざわざgettersetterを準備する必要は全くない。

CL-USER> (defparameter p1 (make-Student :id 10 :name "佐藤"))
P1
CL-USER> (setf (Student-score p1) 80)
80
CL-USER> p1
#S(STUDENT :ID 10 :NAME "佐藤" :SCORE 80)
CL-USER>

上はANSI Common Lispにおける構造体の例だが、CLOS(Common Lisp Object System)と言うオブジェクト指向の機能でも似たような機能になっている。
キーワード引数 :accessor で名前を定義すると、その名前で自動的にアクセサ関数が作成される。

CL-USER> (defclass Student ()
       ((id :initarg :id :accessor id)
        (name :initarg :name :accessor name)
        (score :initarg :score :initform 0 :accessor score)))
CL-USER> (defparameter p1 (make-instance 'Student :id 10 :name "佐藤"))
P1
CL-USER> (setf (score p1) 80)
80
CL-USER> (id p1)
10
CL-USER> (name p1)
"佐藤"
CL-USER> (score p1)
80
CL-USER>

このように、定型的に使われる前提ならプログラミング言語が面倒を見て良い筈なのだ。
言い換えると、プログラミング言語が自動で面倒を見てくれないのならgetter/setterは大して重要じゃないのだ・・・Javaの設計思想が「間違ってる」前提なら分からんけどね。
大体、例えば、メンバ変数/インスタンス変数が10個もあるクラスを定義したとしよう・・・それぞれにgetter/setterを定義したとすれば全部で20個になる。恐らく、書き方にもよるけど一個のgetter/setterは3行は要する。そうすると、getter/setterの集団を作るだけで60行は消費するのだ。スペース入れたりコメント入れたりしたらどうすんだ?単純なクラスの筈なのに一つ100行とかになって、それでなおかつ何らかのメソッドを加える・・・。
分かるだろうか?もう一回繰り返すけど、getter/setterが本当に必要なら自動で暗黙に定義されるべきなのだ。そうすればテキストファイルが肥大化しないで済む。
しかし、プログラミング言語が自動で面倒を見ないとすれば、それは本当は必要無いのだ。つまり、それはJavaでの悪習なのであり、Pythonに持ち込むべきものではない。おわかり?

100歩譲ってgetter/setter付きのStudentクラスを認めたとしよう。
しかし、またこれは何なんだ、と言いたい。

class CalcScore:
 ''' 点数を計算する関数 '''
 def __init__(self):
  ''' 初期化 '''
  self.students = []

 def addStudent(self, student):
  ''' 学生を追加する '''
  self.students.append(student)

 def ave(self):
  v = 0
  for i in self.students:
   v += i.getScore()
  ave_v = v / len(self.students)
  return ave_v

これ、ホンマ何なんや、ってカンジ・・・。
CalcScoreってクラスは計算目的に作ってるクラスだろ・・・。それはまたもやJavaはオブジェクト指向を強要し、クラスを作らないとメソッドが作れないから副次的にこういう書き方をしないといけない、ってだけの話である。
言い換えるとJavaには単純な関数が存在しない、と言う欠点があるのだ。
欠点なんだよ。Javaが正しいオブジェクト指向になってるから、じゃないんだ。
仮にStudentクラスがgetter/setterを持っただけの構造体だとすれば、わざわざJava式のCalcScoreなんつーバカなクラスを使わなくても、次のようにすれば簡単に結果なんぞは計算出来てしまう。

>>> p = [Student(10, "佐藤", 80), Student(11, "鈴木", score = 79), Student(12, "佐々木", score = 84), Student(13, "井上", score = 77)]
>>> import statistics
>>> statistics.mean([i.getScore() for i in p])
80
>>>

これで終了、オシマイ、である。わざわざ無駄CalcScoreなんつークラスは作成せんでもよい。
繰り返すが、Javaはオブジェクト指向を強要するからクラス作成せざるを得ないだけで、Pythonにそんなおかしな流儀を持ち込む必要性なんて全くないのだ。
そしてまたもや百歩譲ってCalcScoreクラスを認めたとしよう。でもこれは無いだろ、って思う。

def ave(self):
 v = 0
 for i in self.students:
  v += i.getScore()
 ave_v = v / len(self.students)
 return ave_v

Battery IncludedのPythonでこんなバカなコード書くなよ、ってのが正直なトコだ。上で見せたがPythonは組み込みで統計関数も持っている。
従って、統計関数を自作するよりも統計関数を「使う」事を覚えるべきである。平均を計算するのならmean関数が存在する。
前回やったmap関数を使うなら、上のaveメソッドは次のように書き換える事が出来る。

def ave(self):
 import statistics
 return statistics.mean(map(lambda x: x.getScore(), self.students))

これで終了、だ。6行あったコードが1/2の3行に圧縮出来た。「元々ある機能」を上手く使うだけで短く目的が達成出来るのだ。
また、短く目的を達成するつもりがないのなら、Pythonを使う必要性もない。

実際のトコ、全体的な内容がどうなってんだか知らんのだけど、いずれにせよ、このオブジェクト指向紹介のコードを見る限り、isam氏の用いているPythonの教科書はあまり良い教科書ではないのじゃないか、と言う疑念を持ってる。
繰り返すが、プログラミングの例題は良問じゃなきゃ、読者をただただ混乱させるだけだし、実際問題「良く書かれた」本はあまりにも少ないのは事実なのである。
困ったモンである。
  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「プログラミング」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事