適切な汎用化のさじ加減
以前(プログラム初心者の頃)から、プログラムは汎用的に作成した方がいいという話が良くあったし、汎用的なプログラムの方が良いと思っていた。
最近、ふと、自分が何を考えてプログラミングしてるかを考えてみた時、それ程汎用的であることを重視していないように感じた。
どの程度汎用的であることが、適切なのかは、様々な背景によるはず。
- そのプログラム(関数、メソッド、クラス)の仕様を修正する可能性があるか?
- 他の人が修正する可能性があるか?
- そのプログラムが、他の機能に比べて使われる頻度が高いのか?
- そのプログラムが、他の機能に比べて様々な場所から使われるか?
一回しか使われないのであれば、汎用的であることの重要度はかなり下がるのではないか。
HTMLを出力するコードで実験
他の機能とのインターフェースの上での汎用性というのは、また別の時に考えるとして、ちょっと、簡単な処理をだめだめなコードから、繰り返しを排除する方向で修正してみる実験をしてみました。
考えられる最悪のコード
#!env python# -*- coding: utf-8 -*-import sysimport codecs sys.stdout = codecs.getwriter('utf_8')(sys.stdout)#headerの出力print u'''<html><body>'''#1人目のデータ id = 1 name = u'山田' age = 18#1人目の出力print u'''<dl><dt>ID</dt><dd>'''print idprint u'''</dd></dl><dl><dt>名前</dt><dd>'''print nameprint u'''</dd></dl><dl><dt>年齢</dt><dd>'''print ageprint u'''才</dd></dl>'''#2人目のデータ id = 2 name = u'増田' age = 19#2人目の出力print u'''<dl><dt>ID</dt><dd>'''print idprint u'''</dd></dl><dl><dt>名前</dt><dd>'''print nameprint u'''</dd></dl><dl><dt>年齢</dt><dd>'''print ageprint u'''才</dd></dl>'''#footerの出力print u'''</body></html>'''
データを取得する処理は、通常データベースから取得すると思うけど、単に変数に代入する形で代用しときます。10年前くらいの業務アプリのコードとかだと普通にこんなコードもあったり。。。こんなコードが21世紀に作成されたとしたら、悪夢なので捨てるべきだと思います。
フォーマット文字列を使えば、print文が減るので読み易くなる
#!env python# -*- coding: utf-8 -*-import sysimport codecs sys.stdout = codecs.getwriter('utf_8')(sys.stdout)#headerの出力print u'''<html><body>'''#1人目のデータ id = 1 name = u'山田' age = 18#1人目の出力print u'''<dl><dt>ID</dt><dd>%d</dd></dl><dl><dt>名前</dt><dd>%s</dd></dl><dl><dt>年齢</dt><dd>%d才</dd></dl>''' % (id, name, age)#2人目のデータ id = 2 name = u'増田' age = 19#2人目の出力print u'''<dl><dt>ID</dt><dd>%d</dd></dl><dl><dt>名前</dt><dd>%s</dd></dl><dl><dt>年齢</dt><dd>%d才</dd></dl>''' % (id, name, age)#footerの出力print u'''</body></html>'''
このバージョンでは、やりたいことが順次的に記述されているので、処理の修正は行ないやすいかもしれない。
テンプレートを1回だけ定義するようにすると記述量が減る
#!env python# -*- coding: utf-8 -*-import sysimport codecs sys.stdout = codecs.getwriter('utf_8')(sys.stdout)#templateの定義 template = u'''<dl><dt>ID</dt><dd>%d</dd></dl><dl><dt>名前</dt><dd>%s</dd></dl><dl><dt>年齢</dt><dd>%d才</dd></dl>'''#headerの出力print u'''<html><body>'''#1人目のデータ id = 1 name = u'山田' age = 18#1人目の出力print template % (id, name, age)#2人目のデータ id = 2 name = u'増田' age = 19#2人目の出力print template % (id, name, age)#footerの出力print u'''</body></html>'''
単純でわかりやすい。
データを纏める
#!env python# -*- coding: utf-8 -*-import sysimport codecs sys.stdout = codecs.getwriter('utf_8')(sys.stdout)#dataを纏める data = [ { 'id': 1, 'name': u'山田', 'age': 18 }, { 'id': 2, 'name': u'増田', 'age': 19 } ]#templateの定義 template = u'''<dl><dt>ID</dt><dd>%d</dd></dl><dl><dt>名前</dt><dd>%s</dd></dl><dl><dt>年齢</dt><dd>%d才</dd></dl>'''#headerの出力print u'''<html><body>'''#全員分出力for u in data:print template % (u['id'], u['name'], u['age'])#footerの出力print u'''</body></html>'''
データの準備する部分と表示を司る部分とが明確に区別されて、MVC的な分担の方法になってきた。
データをクラスで表現する
#!env python# -*- coding: utf-8 -*-import sysimport codecs sys.stdout = codecs.getwriter('utf_8')(sys.stdout)#classclass User:def __init__(self, id, name, age): self.id = id self.name = name self.age = age#dataを纏める data = [ User(1, u'山田', 18), User(2, u'増田', 19) ]#templateの定義 template = u'''<dl><dt>ID</dt><dd>%d</dd></dl><dl><dt>名前</dt><dd>%s</dd></dl><dl><dt>年齢</dt><dd>%d才</dd></dl>'''#headerの出力print u'''<html><body>'''#全員分出力for u in data:print template % (u.id, u.name, u.age)#footerの出力print u'''</body></html>'''
クラスを使うとデータの準備の処理とデータにアクセスする処理が簡潔に記述できるようになる。
クラスを使うことで、データのスキーマの定義と実データを分割することに成功したとも言える。
pythonの場合、このやり方が一番すっきりするような気がする。
クラスの中にhtml生成処理まで組込む
#!env python# -*- coding: utf-8 -*-import sysimport codecs sys.stdout = codecs.getwriter('utf_8')(sys.stdout)#classclass User: htmlTemplate = u'''<dl><dt>%s</dt><dd>%s</dd></dl>''' htmlMappings = [ {'type': 'id', 'displayName': u'ID', 'format': '%d'}, {'type': 'name', 'displayName': u'名前', 'format': '%s'}, {'type': 'age', 'displayName': u'年齢', 'format': u'%d才'} ]def __init__(self, id, name, age): self.id = id self.name = name self.age = agedef toHeml(self): html = ''for mapping in self.htmlMappings: template = self.htmlTemplate % (mapping['displayName'], mapping['format']) html += template % getattr(self, mapping['type'])return html#dataを纏める data = [ User(1, u'山田', 18), User(2, u'増田', 19) ]#headerの出力print u'''<html><body>'''#全員分出力for u in data:print u.toHeml()#footerの出力print u'''</body></html>'''
Userクラスの中に、各人のデータをhtmlで表現するためのメソッドを実装したバージョン。
テンプレートの中の繰り返し部分も排除した。
そのおかげで、全員分出力する処理は、非常に簡単になった。
逆にクラスの中に妙なロジックが含まれてしまって、出力するHTMLを変更しようと思ったときに苦労しそうな感じになってしまいました。
html表現をコントロールしているのがクラスの内部に入ってしまったので、MVC的には綺麗ではない方向に向かっている。
まとめ
普通に上の様な処理を実装しようとしたら、「データをクラスで表現する」のバージョンに近いものになると思う。
でも同様の処理が非常に多い場合などは、「クラスの中にhtml生成処理まで組込む」のバージョンに近いところで実装するかもしれない。
要するに、どのように使われる機能であるのかによって、または、どのような仕様変更が入りやすいかによって、良い実装方法が変わるということで、あまり面白い結論には辿りつかなかった。