もっと詳しく

みなさんこんにちは。いつの間にか夏が終ってしまいましたね。今年はどのような夏を過ごされたでしょうか? 私の3Dプリンタづくり生活は部品調達に難航して完全に止まっています。いろいろとちょっと忙しくて手配ができていないというのもあるものの、本当に各種部品を手に入れるのが大変になってきているんだなぁというのを実感します。ハードウェアの場合は部品が揃わないと何も進まないので、一日も早く解消してほしいものです。

TEXT_痴山紘史 / Hiroshi Chiyama(日本CGサービス
EDIT_尾形美幸 / Miyuki Ogata(CGWORLD)

インターフェイスを適切に管理する

前回は、関数名や処理内容の整理について考えていきました。関数名や処理内容を整理する際に無視できないのが、インターフェイスです。インターフェイスとは何ぞや? ということで検索をしてみると、"インターフェースとは、接点、境界面、接触面、接合面、仲立ち、橋渡しなどの意味を持つ英単語。IT関連では、二つのものが接続・接触する箇所や、両者の間で情報や信号などをやりとりするための手順や規約を定めたものを意味する。" [IT用語辞典 e-Wordsより引用]という記述が見つかります。つまり、関数の引数もインターフェイスですし、GUIのボタンや表示画面も人間とシステムの間を仲立ちするためのインターフェイスになります。

インターフェイスさえしっかりしていて、使用する側もそのインターフェイスの約束をきちんと守ってさえいれば、中身がグチャグチャでも中の仕様が変わっても混乱を招くことはありません。機能のブロックをボコッと外して、同じインターフェイスをもった全然別の機能のブロックをそこにはめると、システム全体が問題なく動くようにできると理想的です。

また、インターフェイスを適切に管理することで、プログラムの物理的・論理的な構造をきれいに保つことができます。前回のGUIから呼ばれる関数の例では、GUIと処理の部分のインターフェイスが曖昧だったことが問題の原因ということになります。

複数のインターフェイスを用意する

インターフェイスをきちんと整備して、一度つくったプログラムを様々なシチュエーションで使い尽くすためにどんなことをすればいいか、簡単なプログラムを例に順を追って考えてみましょう。例として、maファイルを受け取ってabcファイルを出力する関数、convert_ma_to_abc を用意します。

-- convert_ma_to_abc.py --
import os
import sys


def _launch_maya_and_convert_to_abc(src, dst):
    # 何やかんやいろいろがんばってmaファイルをabcファイルに変換する
    return True

def convert_ma_to_abc(src):
    prfx, _ = os.path.splitext(src)
    dst = '%s.abc' % prfx

    result = _launch_maya_and_convert_to_abc(src, dst)
    return result, dst


if __name__ == '__main__':
    src = sys.argv[1]
    result, abc = convert_ma_to_abc(src)
    print(result)

    sys.exit(0)
----

ちなみに、_launch_maya_and_convert_to_abc のように頭に ‘_’ が付いているものは、プログラム内部でのみ使うようにして、外からは触らないでくださいねというPythonでプログラムを書く際のお約束になります。実際には外からも触ることはできてしまうのですが、その場合将来的に挙動が変わったり、関数がなくなってしまうことが考えられるので使用するべきではないです。これもインターフェイスのデザインになりますね。

この例で、既にインターフェイスは2種類用意されています。ひとつがファイルを直接実行する方法です。

> python convert_ma_to_abc.py src.ma

上記のようにして実行することができます。
もうひとつが別プログラムから呼ぶ方法です。

-- convert_folder.py --
import os
import sys
import convert_ma_to_abc

def convert_folder(src):
    for f in os.listdir(src):
        convert_ma_to_abc.convert_ma_to_abc(src)

if __name__ == '__main__':
    src = sys.argv[1]
    convert_folder(src)

    sys.exit(0)
----

単純な機能だけでよければ単体のファイルだけでまかなえますが、各種肉付けをしていくと複数のファイルに分割して管理したくなります。そこでパッケージ化をします。パッケージ化は、フォルダをつくってその中に __init__.py を置きます。また、パッケージ自体を直接実行したい場合(これも新たなインターフェイスですね)は __main__.py を作成します。今回はファイル変換全般を担当するモジュールとして、file_converter を作成します。

+ file_converter
   + __init__.py
   + __main__.py
   + converter.py(旧convert_ma_to_abc.py)
   + utils.py(何やかんや色々するための機能をまとめている)
----

_launch_maya_and_convert_to_abc は utils.py に移動してしまいましょう。

-- utils.py --
def _launch_maya_and_convert_to_abc(src, dst):
    # 何やかんやいろいろがんばってmaファイルをabcファイルに変換する
    return True
----

-- converter.py --
import os
import sys

from . import utils

def convert_ma_to_abc(src):
    prfx, _ = os.path.splitext(src)
    dst = '%s.abc' % prfx

    result = utils._launch_maya_and_convert_to_abc(src, dst)
    return result, dst
----

__init__.py はひとまず空っぽでよいです。__main__.py は以下のような感じになります。

-- __main__.py --
import sys
from . import converter

src = sys.argv[1]
result, dst = converter.convert_ma_to_abc(src)

sys.exit(0)
----

このようにすると、pythonのモジュールとして実行できます。

> python -m file_converter path/to.ma

また、別プログラムからモジュールの機能として呼び出すこともできます。

-- convert_folder.py --
import os
from file_converter import converter

def convert_folder(src):
    for f in os.listdir(src):
        converter.convert_ma_to_abc.convert_ma_to_abc(src)
----

フォルダをまとめてファイルを変換するという処理は頻繁に行われるので、モジュールの機能として取り込みます。utils.py の中に convert_folder を追加しましょう。

-- utils.py --
import os

from . import converter

def _launch_maya_and_convert_to_abc(src, dst):
    # 何やかんやいろいろがんばってmaファイルをabcファイルに変換する
    return True

def convert_folder(src):
    for f in os.listdir(src):
        converter.convert_ma_to_abc.convert_ma_to_abc(os.path.join(src, f))
----

これで、フォルダをまとめて変換することもできるようになりました。そして、各機能がミル・クレープのように奇麗にレイヤーに分かれており、それぞれにアクセスするためのインターフェイスも整備されています。

また、変換処理を行うための _launch_maya_and_convert_to_abc が外から隠蔽されているため、ここの処理内容が変わったり、関数そのものが整理されてしまっても file_converter モジュールのユーザーに影響を及ぼすことはありません。

裏口への対応

システムを構築する際、"何かあったときのために、ここは手動で対応できるようにしたい" とか、"〇〇という、たまにある例外のために別の口をつくっておきたい" と相談されることがよくあります。こういう、本来のながれとは異なる処理をするための裏口はインターフェイス整備の対極にあるもので、こういった話が出たときは警戒度MAXで臨む必要があります。

もちろん全てのケースに対して万全なシステムを構築することはできないので、ある程度裏口を用意する必要もあります。その場合でも最低限の道の整備をするとか、裏口から本流に戻るための迂回路を用意しておきます。

裏口への対応コストは結構馬鹿にならないもので、裏口が増えれば増えるほど(そして、放置しておくと無尽蔵に増える)爆発的にコストが増えていきます。たとえば、オンオフのスイッチをひとつ作成するだけでも考えなければいけないケースは倍に増えることになります。これが2つ、3つ……と増えていくと倍々になっていくので、10個のスイッチを用意しただけで1,024通りのバリエーションができることになります。この全てのケースに裏口と迂回路を用意することを考えると、ちょっと現実的ではないと思えてきますよね。でもユーザーから見えているのはたった10個のスイッチでしかないのです。ここで "たった1個のボタンを追加するだけでしょ?" と言われたときに、ユーザーにボタンの追加が難しいことを納得してもらうのはとてもとても難しいです。

裏口を無理やりこじ開けて好き勝手するような例としては、便利だからという理由で _launch_maya_and_convert_to_abc を外部から勝手に呼び出して使用するような例があります。これはさすがにちょっと面倒みることはできませんねーってなってしまいますね。

まとめ

今回例に上げた内容のひとつひとつは、Pythonでプログラムを書くときに最初に勉強する基本的な内容です。それでも、インターフェイスという視点をもって眺めると結構いろいろと考えることがあります。通常のプログラムであれば今回の例からさらにクラスを使用したり、GUIを用意するというように、どんどん規模が大きく、複雑になっていくでしょう。それでもインターフェイスと内部の構造がきれいに整理されていれば、規模が大きくなっても管理の複雑さを抑えることができます。

第38回の公開は、2021年10月を予定しております。

プロフィール


  • 痴山紘史
    日本CGサービス(JCGS) 代表



    大学卒業後、株式会社IMAGICA入社。放送局向けリアルタイムCGシステムの構築・運用に携わる。その後、株式会社リンクス・デジワークスにて映画・ゲームなどの映像制作に携わる。2010年独立、現職。映像制作プロダクション向けのパイプラインの開発と提供を行なっている。新人パパ。娘かわいい。
    @chiyama