SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

よろずプログラマーのためのPython導入ガイド

Java meets Python - 第2回 配列と別れる50の方法

よろずプログラマーのためのPython導入ガイド (4)

岬めぐり:コードを巡る旅の縁(よすが)に

 同じ景色を眺めても、思い入れがあるのとないのとでは、その感動も違ってきます。そこで、漠然とコードを眺めるのではなく、構造化プログラミング(SP)とオブジェクト指向プログラミング(OOP)との違いを示しながら、その相互理解を深めます。

そこにコマが移動するのは

 移動できるコマは、空いたマス目と隣接するものだけです。配列を用いた伝統的な手法では、これを配列の値を入れ換える操作として実現します。そのとき、主役を演じるのは配列で、値は脇役にすぎません。OOPでは、これをオブジェクト間のメッセージ送受信としてモデル化します。そこでは、コマ(オブジェクト)が主役を演じます。

 2つのオブジェクトが「座標を交換swapしませんか」というメッセージ(what)をやり取りする様子を、メソッド(how)として実現することを目指します。つまり、Tileインスタンス(コマ)とNull(空いたマス目)とが「座標を交換したい」とする意思の伝達と考えます。そこでは、空いたマス目を特殊なコマと見なして、既存のコードを再利用します。また、目的(what)と手段(how)とを明確に区別する戦略は、関心の分離(separation of concerns)の趣旨にも適います。

class Tile(Shape):
    def swap(self):
        self.x, Null.x = Null.x, self.x
        self.y, Null.y = Null.y, self.y

 メソッドswapでは、タプルを使って値を交換します。演算子=の両辺では、左辺に列挙した変数が順に、右辺の対応する値を束縛(結合)します。すると、式a,b = b,aによって、変数a/bが参照する値を交換する操作を、簡潔に表現できます。

あなたは肉体派/頭脳派
 コマを配置した後でどのように移動させても、ゲーム盤からは飛び出しません。なぜなら、座標を交換する以外に、移動する方法(メソッド)を提供(公開)していないからです。この問題解決の手法は、懸賞問題が解けないことの証明にも繋がります。単純作業だけに頼る肉体労働(テストファースト)を脱して、頭脳労働(形式手法)を駆使した成果は、大幅なコスト削減と高度な品質保証として実を結びます。

そこに条件式が介在するのは

 カプセル化によって、インスタンス属性x/yと、メソッド操作moveをひとまとめにして、公開インターフェースを明確に規定すると、不適切な操作は適用できません。そのプロトコルに従うなら、例外が発生する余地すらなくなります。

 先のJava版のコードと、次のコードの断片にある条件式とを比較してください。

class Tile(Shape):
    def move(self):
        if self.x-1 == Null.x && self.y == Null.y:
            self.swap()
            return True
        if self.x+1 == Null.x && self.y == Null.y:
            self.swap()
            return True
        if self.x == Null.x && self.y-1 == Null.y:
            self.swap()
            return True
        if self.x == Null.x && self.y+1 == Null.y:
            self.swap()
            return True
        return False

 同じ条件式でも、次のように、

if (x>0 && board[y][x-1] == n) {

 (配列に起因する)例外を避けるための便宜的な手段(how)と、次のように、

if self.x-1 == Null.x && self.y == Null.y:

 (ゲームの規則)要求仕様に記載した目的(what)とでは、その趣旨が異なります。what/howを分離すると、コードの見通しがよくなるだけでなく、例外も発生しないので、一石二鳥です。

あなたがバグの温床を
 要求仕様の変更に伴う作業では、ささいなアプローチの違いが、膨大なメンテナンス費用として跳ね返ってきます。ゲームの難度を上げるために「マス目の数を増やしたり、3次元パズルへと発展させたい」と提案します。しかし、その企画を実現するために、本来の再開発よりメンテナンスに労力を割かれるなら、たとえ単純な作業でも意気消沈します。ソースコードを住処とするバグは、プログラマの心の隙間を見逃しません。何処に抜けがあるか、虎視眈々と狙っています。狭くて込み入った箇所を好む習性は、ソフトウェアの世界に棲むバグにも見られます。

リファクタリング:ピタゴラスの定理を使って

 隣接するコマの位置関係を判定するのに、ピタゴラス(三平方)の定理を用いると、条件判定を整理整頓(リファクタリング)して、見通しの良いコードを記述できます。

class Tile(Shape):
    def move(self):
        b = self.isInContactWith(Null)
        if b: self.swap()
        return b
    def isInContactWith(t1, t2):
        dx = t1.x - t2.x
        dy = t1.y - t2.y
        return dx*dx + dy*dy == 1

 メソッドmoveを実現するときには、4つの条件判定に共通の論理構造を抽出した、isInContactWithを再利用します。三平方の定理を使って、2つのコマの距離が1と等しいかどうか(隣接するか)を判定します。条件を判定するだけなので、平方根を求める必要はありません。自乗和を求めれば十分です。コードの見通しがよくなると、バグを発見するのも容易になります。

selfの事情
 Pythonにおけるselfは、Javaにおけるthisと違って、コンパイラによって強制されるものではありません。規則に縛られず、プログラマが慣習に従って自主的に用います。isInContactWithにおいて、selfではなく、t1/t2を用いたのは、その相称性(主従を規定しない)を読み手(プログラマ)に伝えたかったからです。

次のページ
岬めぐり:コードを巡る旅の縁(よすが)に《承前》

この記事は参考になりましたか?

よろずプログラマーのためのPython導入ガイド連載記事一覧

もっと読む

この記事の著者

小泉ひよ子とタマゴ倶楽部(コイズミヒヨコトタマゴクラブ)

http://tamago-club.cocolog-nifty.com/「楽しくなければ仕事じゃない」が私たちのモットー。99%の苦悩の連続も、1%の成功に報われます。だからこそ、この仕事が楽しくて仕方がないのです。楽をするための努力なら惜しみません。何もせず楽をしているのと、努力をしたから楽ができるのと...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

伊藤うさぎ(イトウ ウサギ)

ペンネームの「由来は」と言うと。苗字の方は、セミナー研修で同じチームになった、3人の合体ユニット名 [I:石塚, T:田川, O:尾沢] から来ています。名前の方は、同じ干支(卯:1987 年生)に因んだものです。既に2人は卒業して、残る1人がその名跡を継承しています。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1860 2007/11/13 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー