Make組ブログ

Python、Webアプリや製品・サービス開発についてhirokikyが書きます。

DjangoのORMが実行してるSQLの見方 (とdjango-debug-toolbar のススメ)

DjangoのORMが実行してるSQLの見方 (とdjango-debug-toolbar のススメ)

djangoddebugttoolbar 使おう。

以下の様な記事があった。

django.db.connection.queries を使うとよいそうです。でもこれを覚えるのはちょっと大変。

ここで djangoddebugttoolbar 使えばもっと楽にできるのでオススメしたい。 どうするのかというと、 djangoddebugttoolbar の debugsqlshell という機能を使う。 これは普段の manage.py shell とほとんど同じなんだけど、SQLが実行されたときは、そのSQLを表示してくれるというもの。

(djangoddebugttoolbar は画面上にデバッグ用のパネルを表示してくれたりする、頼もしいやつ)

djangoddebugttoolbar をインストールすると管理コマンドで debugsqlshell というのが 使えるようになるので、設定とかもそれほど必要ない。

インストール:

$ easy_install django-debug-toolbar

設置ファイルに記述:

# settings.py
INSTALLED_APPS += ('debug_toolbar',)

ってすれば、もう使える。 djangoddebugttoolbar の機能をモリモリ使うなら他にも設定は要るけど、 debugsqlshell だけなら、まぁこれでもOK。

# $ ./manage.py debugsqlshell

>>> from django.contrib.auth.models import User
>>> User.objects.all()
SELECT "auth_user"."id",
       "auth_user"."username",
       "auth_user"."first_name",
       "auth_user"."last_name",
       "auth_user"."email",
       "auth_user"."password",
       "auth_user"."is_staff",
       "auth_user"."is_active",
       "auth_user"."is_superuser",
       "auth_user"."last_login",
       "auth_user"."date_joined"
FROM "auth_user" LIMIT 21  [0.40ms]

[<User: hirokiky>]
>>> qs = User.objects.all()[:10]
>>> qs
SELECT "auth_user"."id",
       "auth_user"."username",
       "auth_user"."first_name",
       "auth_user"."last_name",
       "auth_user"."email",
       "auth_user"."password",
       "auth_user"."is_staff",
       "auth_user"."is_active",
       "auth_user"."is_superuser",
       "auth_user"."last_login",
       "auth_user"."date_joined"
FROM "auth_user" LIMIT 10  [0.35ms]

[<User: hirokiky>]
>>> 

こんなかんじ。 クエリセットが評価されてSQLが投げなれるタイミングもよくわかる。

でも djangoddebugttoolbar の本領はこんなもんじゃなくて、画面上にデバッグ用のパネルを表示してなんぼのもの。 使うデバッグ用のパネルは設定で選べるんだけど、とくに良いのが debug_toolbar.panels.sql.SQLDebugPanel 。 これを追加してあげれば、1リクエスト中に走ったSQLと、その実行時間をガントチャートで表示してくれる。 やたら遅い画面とかあるときに原因 (クエリ1000件以上投げてるじゃん!とか) をすぐ発見できるのでオススメ。

djangoddebugttoolbar がいないと生きていけない。

django-celeryで非同期処理クイックスタートガイド

djangoccelery で非同期処理をやる。

サクッと非同期処理を試せちゃうような、クイックスタートガイド をメモがてら書いていく。

インストール

まずはインストールから。

pip を使って Djangodjangoccelery をインストールする。

% pip install Django
% pip install django-celery

おわり。

バージョンは以下のようになった

% pip freeze

Django==1.5
amqp==1.0.10
anyjson==0.3.3
billiard==2.7.3.23
celery==3.0.16
django-celery==3.0.11
kombu==2.5.8
python-dateutil==1.5
pytz==2013b
wsgiref==0.1.2

設定

インストールできたら Django のプロジェクト asynctest と、 アプリケーション caculator を作ってみる。

% django-admin.py startproject asynktest
% cd asynktest
% python manage.py startapp caculator

まずは設定ファイル (asynctest/asynctest/settings.py) に djangoccelery と先ほど作った caculator 、それとあと 裏方の kombu.transport.django をいれてやる。

INSTALLED_APPS = (
    ...
    'djcelery',
    'kombu.transport.django',
    'caculator',
)

こんなかんじに。

さらに、 djangoccelery に関する設定を追記する。

###### django-celery configuations ######
from djcelery import setup_loader
setup_loader()
BROKER_URL = 'django://'
# Tasks will be executed asynchronously.
CELERY_ALWAYS_EAGER = False

あとはDBと同期すればOK。

note

django-kombu をインストールしなくても最近の kombu と上記の設定で Django のDBをブローカーとして使えるよう。 djangoccelery インストールの段階で kombu もインストール されてくれるので、とくに kombu を意識する必要がなくなって しまった。

非同期処理させるもの

さて肝心の処理をおこなう関数を書く。 caculator/tasks.py を以下のように書いた。

import time

from celery import task

@task
def add(a, b):
    time.sleep(10)
    return a + b

task でデコレートしてあげるだけで良い。 add 関数は10秒スリープしてくれるという親切設計なので、 存分に非同期を味わうことができる。

あと、モジュール名は tasks.py にしましょうね。

実行してみる

準備ができたので非同期を味わってみる。 まずは Celery 氏を起動。

% python manage.py celeryd -l info

別のシェルから manage.py shell を起動。

% python manage.py shell

打っていく。

>>> from caculator.tasks import add
>>> add(1, 2) # 10秒かかる
3
>>> add.run(1, 2) # 10秒かかる
3
>>> result = add.delay(1, 2) # 非同期で実行するには delay
>>> result.ready() # ready で終了したかがわかる
False
>>> result.ready() # マダァ?(・∀・ )っ/凵⌒☆チンチン
True
>>> result.get() # 終わってたら get でとる
3
>>> # 処理終わってないのに get すると、
>>> # 値が返るまで待ってしまう
>>> # タイムアウトしたい
>>> 
>>> result = add.delay(3, 4) # もっかい計算
>>> result.get(timeout=3) # 3秒間だけ待つ
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/path/to/celery/result.py", line 109, in get
    interval=interval)
  File "/path/to/celery/backends/base.py", line 186, in wait_for
    raise TimeoutError('The operation timed out.')
TimeoutError: The operation timed out.
>>> # 3秒以内に終わらなかったら TimeoutError
>>> result.get(timeout=3)
7
>>> # 3秒以内なら結果が返る

こんなかんじで使えます。 やったね。非同期処理ができたよ。

重い処理があれば非同期でやらせて、ポーリングして結果待つとか サクッと書けてしまう。 Django 内で完結してできるから分かりやすくて楽。 djangoccelery 覚えていて損ないかと思う。

参考:

「それZopeだよ」って言われたので作ったもの

「それZopeだよ」って言われたので作ったもの

URLディスパッチャーとか飽きたので、もっと他のことがしたかった。 URL(リクエストのPATH INFO)にマッチしたパターンに対応するビューなど言ってないで、 より汎用的な構造はないかと考えていた。

思いついてつぅいーとしてた:

URLディスパッチャとか無しで、「リクエストオブジェクトを受け取り、レスポンスオブジェクトを返す、呼び出し可能オブジェクト」が、自身のパスにマッチしないリクエストであれば他の呼び出し可能オブジェクトに移譲するっていう何かを妄想した ( https://twitter.com/hirokiky/status/312183468127834113 )

というと「それZopeっていうんだよ」と教えてもらった。

あとは Traversal というのもあるらしかった。

今日、ちょっとTraversalを試して、分かった気になったりもしたけど、 結局自分で書いてみないと納得いかないので、書いた。

書いたもの

これ

ただ「自身のパスにマッチしないリクエストを移譲する」のではなく 「子を呼ぶ条件にマッチすると、それに移譲する」ものを書いた。

node というデコレーターを使って、どのWSGIアプリケーションが呼び出されるかを定義する。 gistにもあるサンプルを下にそのまま貼ったので、それで説明していく。

@wsgify
def node2(request):
return "OK. I'm node2"

@wsgify
@node({'a': 'node1', 'b': 'node2'})
def node3(request):
return "OK. I'm node3"

@wsgify
@node({'a': 'node2', 'b': 'node3'})
def node1(request):
return "OK. I'm node1"

if __name__ == '__main__':
httpd = make_server('', 8080, node1)
httpd.serve_forever()

各関数に付いている node というデコレーターが今回書いたもの。

ここではまず node1 が呼ばれる。けど @node の引数に渡されている辞書とPATH_INFO をみてマッチするものがあれば、さらにその子が呼ばれる。 例えば /a/ にアクセスされている場合、 node1 のマッピングに a が存在しているので node2 が呼ばれる。 node2 には @node がないのでそのままレスポンスが返される。

マッチするものがなかったら (例えば /c/) デコレートされている関数 (この場合 node1) がそのまま呼ばれる。

/b/a/ という場合、まず /b/ にマッチする node3 が呼ばれる。 1度呼び出しがあると次の @node は / 区切りで、その隣を見る。ここでは a 。 node3 では a に対して node1 が当てられているので /b/a/ では最終的に node1 が呼ばれることになる。

ちなみに辞書の値には関数の文字列を与えればよく、呼び出し時に読み込まれるので、関数を書く順番には 気を遣わなくていい。

拡張するなら

今回は辞書と、それに path_info がマッチしているかをみているだけだったが、 リクエストオブジェクトを受け取ってブール値を返す関数を与えて、その返り値が True の 場合に子を呼び出すようにすれば、どのような条件にも対応できる。 あるいはそのような関数のタプルを渡して、それらがすべて True の場合に呼び出すと、 PyramidのPredicateっぽくなるかもしれない。

できれば単にデコレーターで受け継ぐだけじゃなくて、その間に処理を挟みたい。 他の呼び出しがある前に、リクエストオブジェクトに対して何らかの処理を すればいいだけなので、それでミドルウェアの実装もできそうではある。

あとは「自身にマッチしなければ移譲する」ものを作りたかったので、それについて考えるのもいいかも しれない。

SQLAlchemyのSQL表現言語で集計する

SQLAlchemyのSQL表現言語で集計する

前回の Djangoで売上を集計/集約処理する に続いて、また集計します。

今回はDjango(のORM)ではなく SQLAlchemy を使います。バージョンは0.8。 ただしORMとしてではなく、SQLAlchemyのSQL表現言語(SQLExpression)のみ使います。 (私はSQLAlchemyのド素人で、ORMとして使ったことがないです。ただ、SQLAlchemyの SQL表現言語が素晴らしいなーと思ったので、試してみました)

SQLExpressionのチュートリアルも参考にしてください:

前回同様ユースケースにあわせて、集計をしてみます。

今回も:

  • 売上合計金額/件数の算出
  • 円グラフの算出
  • ランキングの算出
  • 折れ線グラフの算出

をやってみます。

さて、今回も考えるのはお人形屋さんです。このお人形屋さんの売上情報、商品の情報 などをもとに集計処理を行なって行きましょう。

想定するデータ構造

はじめにテーブルから見ていきましょう。

テーブルは3つで、売上情報、商品と商品のカテゴリーです。 以下のようにテーブル定義を書きました:

categories = Table('categories', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String),
)
items = Table('items', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String),
    Column('price', Integer),
    Column('category_id', None, ForeignKey('categories.id')),
)
histories = Table('histories', metadata,
    Column('id', Integer, primary_key=True),
    Column('item_id', None, ForeignKey('items.id')),
    Column('sold_datetime', DateTime)
)
  • histories: 売上情報/履歴。商品へのFK(item_id)と、その商品が売れた日時 (sold_datetime)を持っています
  • items: 商品。商品名(name)、価格(price)、カテゴリーへのFK(category_id)を持って います。
  • categories: カテゴリー。カテゴリー名を持っています。

売上合計金額/件数の算出

早速集計といきましょう。

jan = (datetime.datetime(2012, 1, 1), datetime.datetime(2012, 2, 1))

# 2012年1月の売上件数
select([func.count()],
       histories.c.sold_datetime.between(*jan))

# 同期間売上金額
select([func.sum(items.c.price)],
       histories.c.sold_datetime.between(*jan) & \
       (items.c.id == histories.c.item_id))

先に jan に、betweenに渡す引数を作っています。

円グラフの算出

円グラフ、GROUP BYしてSUMですね。 せっかくカテゴリーというテーブルを設けたので、カテゴリーごとの売上金額を求め ましょう。「売上の6割はウィッグなんだねぇ」とかが分かるわけです。 (そうなんだ。すごいね)

# 2012年1月のカテゴリーごとの売上金額
select([categories.c.name, func.sum(items.c.price)],
       histories.c.sold_datetime.between(*jan) & \
       (items.c.category_id == categories.c.id) & \
       (items.c.id == histories.c.item_id)).\
       group_by(categories.c.id)

ランキングの算出

商品ごとのランキングを算出します。 商品でGROUP BYしてSUMとって、それでORDER BYですね。 これで1番売上をあげている商品が分かります。

# 2012年1月の商品ごとの売上金額のランキング
select([items.c.name, func.sum(items.c.price).label('total_price')],
       histories.c.sold_datetime.between(*jan) & \
       (items.c.id == histories.c.item_id)).\
       group_by(items.c.id).\
       order_by('total_price'),

.label() で商品ごとの売上金額に名前つけて、それを .order_by() で指定して います。 商品が増えてきたら .limit(100) などを追加するのも良いかもしれません。

折れ線グラフの算出

折れ線グラフというのは横軸に日時、縦軸に売上金額(もしくは件数)をとったものを 考えます。日付はある単位ごとにまとめたものになりますね。例えば日毎の売上、月毎の 売上などです。

# 全期間の日次売上金額
select([func.date(histories.c.sold_datetime).label('sold_date'),
       func.sum(items.c.price)]).\
       group_by('sold_date'),

# 全期間の月次売上金額
select([func.strftime('%Y-%m', histories.c.sold_datetime).label('sold_date'),
       func.sum(items.c.price)]).\
       group_by('sold_date')

前回 では なかなか苦戦した覚えありますが、そうでもない感じですね。

売上がない日/月はそもそも結果にでないのでアプリ側で補完するなり、もっと良い方法 を考えるなりしてください。

おわりに

けっこう分かりやすいし、書きやすいですね。

今回書いたものはGistにあげています:

面倒臭かったのでベタベタに書いていますが、まぁいいでしょう。

ただ1点、後々「いいなー」と思った書き方:

import sqlalchemy as sa

sa.create_engine(...)

これのように sqlalchemy そのままimportしておいて、毎回書いてやることですね。

あとはまぁ、SQL表現言語を、SQLAlchemyのORMと併せて使ってやるのも面白いようです。 なので気が向いたらやってみようと思います。

Djangoで売上を集計/集約処理する

Djangoで売上を集計/集約処理する

集計/集約します。 (この記事ではaggregationを「集計」と訳します。集約、よりもヒットしやすそうだったから)

Django 1.4 日本語ドキュメント(暫定) ではこちらにあります。

ちなみにDjangoでaggregationの機能が追加されたのは1.1からですね。 (1.1 リリースノート)。

正直、上記のドキュメント読めば集計処理の大体は網羅できると思います。 ですが今回はユースケースにあわせて、集計の機能を紹介します。

今回は

  • 売上合計金額/件数の算出
  • 円グラフの算出
  • ランキングの算出
  • 折れ線グラフの算出

をやってみます。 これくらいの項目があれば十分ですかね。 お店の売れ行きや人気商品は掴めると思います。

さて、今回考えてみるのはお人形屋さんです。 とあるお人形屋さんの売上情報をもとに、集計処理を行なって行きましょう。

想定するデータ構造

はじめにテーブル/Djangoのモデルからみていきましょう。

必要なモデルは2つで、お店に並ぶ商品と売上情報です。 models.pyは以下のようになると思います。

from django.db import models
from django.contrib.auth.models import User

class Item(models.Model):
    """お店の商品
    """
    name = models.CharField(u"商品名", max_length=255)
    price = models.PositiveIntegerField(u"価格")


class SalesHistory(models.Model):
    """売上情報
    """
    item = models.ForeignKey('Item')
    user = models.ForeignKey(User)
    sold_datetime = models.DateTimeField(u"販売日時")

こんなところかと。 では早速集計をしていきましょう。

note

今回は省きますが、Itemには他にも商品のカテゴリや販売元などのフィールドが追加できると考えられますね。 例えば販売元ごとに折れ線グラフを集計してやることで「今どこの販売元の商品が注目されているか」を みることができちゃうと思います。

売上合計金額/件数の算出

簡単なところから、売上合計金額と件数です。

集計の対象期間は「今月」として話をすすめます。つまり今月の初日から昨日までが対象です。 まずはその期間内のSalesHistoryを取得してみましょう。

>>> import datetime
>>> from dateutil.relativedelta import relativedelta

>>> today = datetime.date.today()
>>> first_of_thismonth = today + relativedelta(day=1)

>>> from dollshop.models import SalesHistory
>>> SalesHistory.objects.filter(sold_datetime__range=(first_of_thismonth, today))

こんなところと思います。 当日のdate、relativedeltaを使って月の初日(first_of_thismonth)をとりました。 最後の行でSalesHistoryをfilterしてます。

>>> sales_of_thismonth = SalesHistory.objects.filter(sold_datetime__range=(first_of_thismonth, today))

>>> # 売上件数
>>> sales_of_thismonth.count()

>>> # 売上金額
>>> from django.db.models import Sum
>>> sales_of_thismonth.aggregate(Sum('item__price'))

簡単ですね。 aggregateを使います。lookupはSum。

円グラフの算出

>>> # 商品ごと
>>> sales_of_thismonth.values('item').annotate(total_price=Sum('item__price'))

>>> # ユーザごと
>>> sales_of_thismonth.values('user').annotate(total_price=Sum('item__price'))

商品ごとなりユーザごとの合計金額を求めて、アプリ側で各々の金額を全部の合計金額で割れば比率はでますね。 まぁ大抵のJSライブラリはそのまま値で渡せばよしなにやってくれます。

この例では「商品ごと」というなんともビミョーな円グラフですが、応用すれば「商品のカテゴリごと」などもできますね。

ランキングの算出

>>> # 金額順
>>> sales_of_thismonth.values('item').annotate(total_price=Sum('item__price')).order_by('-total_price')

>>> # 件数順
>>> sales_of_thismonth.values('item').annotate(numof_sales=Count('id')).order_by('-numof_sales')

さきほどの円グラフの算出にそのままorder_byをつけただけです。 ‘-'を付けてるのは降順にするためです。

折れ線グラフの算出

折れ線グラフというのは横軸に日時、縦軸に売上金額(もしくは件数)をとったものを考えます。 日付はある単位ごとにまとめたものになりますね。例えば日毎の売上、月毎の売上などです。 1年間の売上推移を見るのに日毎の集計をしちゃったら、横に365点とることになるんで非常に見難いですよね。

>>> # 日毎
>>> sales_of_thismonth.extra({'sold_date': 'strftime("%%Y%%m%%d", sold_datetime)'}).values('sold_date').annotate(total_price=Sum('item__price'))

>>> # 月毎
>>> sales_of_thismonth.extra({'sold_month': 'strftime("%%Y%%m", sold_datetime)'}).values('sold_month').annotate(total_price=Sum('item__price'))

extraで、values-annotateのグループ化に使う値を作ってあげてます。 日毎の集計ならstrftimeじゃなくてdateでもいけそう。

おわりに

意外とあっさりできましたが、理解するのはちょっとややこしいかも。 annotateの前にvaluesをおいてグループ化してやってるわけですが、SQL脳でみると.group_byなどのメソッドが欲しいなというところ。

これについては何度も議題にあがってるようです。

よりORMらしい方法(values-annotateのことかな)が採択されたとのこと。詳細は チケット3566 のようで、このチケットは「ORMで集計できるようにしよう」という提案のようですね。 さっと見てみたところ、もともとの提案ではgroup_byなどであったようですが、まぁ時間のあるときにでもじっくり見ときましょ。

DjangoのAUTHORSに追加された

DjangoのAUTHORSに追加された

先日、Djangoに送っていたプルリクエストが取り込まれた。 そんで AUTHORS (Djangoに貢献したことがある人が書いてあるテキスト) に追加された。

初めてのことで嬉しいのでブログに書いてます。 あとは Django のコード書く人増えたらいいなと思って書く。

プルリクエストしたもの

HttpResponseRedirect* に .url という属性を追加するという、小さな新機能です。 この属性は単にレスポンスヘッダの Location の値を返すもの。つまりは

url = property(lambda self: self['Location'])

こういうことです。 今まで response['Location'] と書いていたのが response.url で済むわけです(素敵!)。 そもそもなんで Location なんてヘッダの名前覚えとかないといけないのか、とね。

ちなみに実装上で、この値を頻繁に読むということはないけど、テストでよく使いますね。

報告者の coolRR さん、取り込んでくれた claudep さんありがとうございます。

Djangoを読み書きしよう

上記の対応が取り込まれて、はれてDjangoAUTHORS に追加されました。日本人では4人目ですかね。

4人しかいないのはちょっと寂しいので、Django使ったことあるよという方はぜひパッチ/プルリクエストを 送ってみましょう。 Djangoソースコードを読んで書くのはそれなりに楽しいと思う。

私はHTTP Handlingなんかが好きで読んでます。 django.core.handlers.base などを読んでみると 処理の流れが見えて面白い。あとはWSGIについて知ってみて、そこから django.core.handlers.wsgi を読めばもっと良いと思う。

そういうと2月23日に世界3箇所で DjangoSprint が開催されます。 日本でも便乗してやります 。 ぜひこの機会にDjangoのコードを読んでみて書いてみましょう。

私はそこでもDjangoのチケットに対応するなりをしたいと思ってる。

ではまたそのときに。

何歳からプログラミングを始めるべきか

何歳からプログラミングを始めるべきか

「プログラミングは何歳から始めるべきでしょうか」

これはよくある質問だと思う。 最近またTwitter上でこれに関する議論、疑問、嘆きなどが見られるので、 いい機会だと思って書いてみる。

何歳であれ適齢です

私の大好きな How to become a hacker という文章には:

始めようというやる気になったのなら、何歳であれ適齢です。
大抵の人は 15~20 歳で興味を持つようですが、私はその上下ともに例外を知ってます。

とある。 この文章は非常に参考になるので、この時点で論破かもしれない。 でも待ってほしい、それはさすがに暴論だし、知的じゃない。なにより「ハッカーになろう」と 「プログラミングは何歳から始めるべきか」では話が違う。

さて「プログラミング」って何だろうか。 そもそもなぜ「プログラミングは何歳から始めるべきか」、「私は始めるのが遅かったのではないか」と思うんだろうか。

疑問を持つことに、疑問をもつべきではないか

そもそも「プログラミングを始めるのが遅すぎたかな」と考え始めた時点で、 「なぜ自分がプログラミングをしたいのか」を考えたほうがいい。 もちろんこれはプログラミングに限ったことじゃなくて、例えばエレキギターでも油絵でもいい。

とにかく思うのは「遅すぎたかな」と考えた時点で自分の行動原理(つまり、何故その 行動を起こしたいかということ)にブレがあるんじゃないかということ。 例えばそれが「ソフトウェアに魅了されていて、それについてもっと知りたい」 という知識欲なら「プログラミングを始める年齢」は関係ない。

もちろんそんな単純ぽっきりな理由だけで毎日キーボードを叩く人はなかなかいないと思う。 たぶん他には人の役にたちたいという気持ちだったり、人生に意味を残したいという 気持ちかもしれない。誰かに憧れてるのかもしれないし、ハッカーになりたいのかもしれない。 あるいは生存戦略とか自己顕示欲とか(他人からしてどうでもいい)理由かもしれない。

そういった理由があるからプログラミングを始めるのであって、今更「プログラミングを始めるには遅かったか」 と考える必要なんてない。 たしかに「あぁもう少し早く始めていたら」という気持ちはわかるけど、今更後悔してもしかたがない。 むしろプログラミングをしていなかったときにも大切な何かを学んで、それが今にも活かされているはず。 そう考えるほうが精神的に楽だし、よりプログラミングのことを考えられる。

でもまぁ気持ちはわかる。 じゃぁなぜ「あぁもう少し早く始めていたら」と思ってしまうんだろう。

投資への恐怖心

「自分がプログラミングに捧げた時間が無駄だった」と思うのが怖いんじゃないか。

ロールプレイングゲームの話をしよう。 キャラクターは敵を倒すことによって経験値を得て、そしてその経験値によってレベルが 上がる。ゲームによっては「スキル振り」というものがあって、得た経験値をプレイヤーが 任意の「スキル」もしくは「ステータス」に割り振ることができるというもの。例えば 攻撃力が低くてもいいから、とにかく素早いキャラクターを作りたいのなら「素早さ」という ステータスに経験値を多く割り振って、多く「素早さ」のレベルを上げる。その代わり「攻撃力」 や「防御力」をあんまりあげずに、「魔力」は一切上げないというような育成をするだろう。 「素早さが取り柄」のキャラのはずが中途半端に「魔力」をあげてもしかたがない。 素早さを活かした回復が目的なら、そいつには積極的に回復アイテムを使わせるべきだよね。 でもここでちょっとミスって「魔力」に経験値を振ってしまったらどうしよう。これは完全に「育成ミス」 で、ハッキリ言ってリセットしちゃうかもしれない。

でも人生にはリセットがない。 おまけに時間は限られていて、得られる経験値には上限がある。余計なことには時間を使っちゃったら、もうそれは絶対に返ってこない。 やっぱりこれが怖いんじゃないか。

  • 「自分はプログラミングにこれだけの時間を捧げたけど結局それそのものが不要だった」
  • 「始めるのが遅すぎた。捧げた時間のわりには中途半端なレベルにしかなれなかった」

「盗賊のわりには素早さが足りない」、「魔法使いのわりにはキャパシティ(MP)が少ない」。 そんな「育成ミス」をしたくないんじゃないか。

そして人生というのには攻略本もないから、「本当に『素早さ』だけを上げるべきか」も分からない。 今、「自分がどんな風にスキルが振られているか」もわからない。

本当に現実というやつはクソゲーなんだ。完璧主義者にとってはとくにそうかもしれない。

何がしたいかを考える

でも待ってほしい。現実っていうゲームは「しきい値」がない。 「しきい値」というのは、例えばレベルアップのしきい値のことで、「経験値があと20あればレベルアップしますよ」といったもの。 だから時間を捧げれば、その分きちんと自分は変わってくれる。 たしかに何らかの基準、例えば学力試験だったり給料だったりは存在するけど、それはあくまで外部からの評価だよね。 「基準」であって、根本的に自分がどうありたいかとは関係がない。

そしてそもそも人生には「ロール」がない。 「ロール」なんて無いんだから、考えるべきは「何がしたいか」の一点でしかないんじゃないか。

私はハッカーに憧れている。そして「ハッカーになろう」というのは外部評価に依存している。 たしかに、私はハッカーに憧れているけど、「ハッカーになるためを第一に考えて行動はしたくない」 考えるべきは「何がしたいか」であって、根本的に私は「何かを面白いものを作りたい」、 「自分の技術で意味あるものを残したい」と考えている。

そういった自分基準の行動原理を持てば「プログラミングを始めるのが遅すぎたのではないか」などという考えは非常にどうでもよくなってくる。 だって自分が納得すればそれでいいんだから、遅すぎるも早すぎるもない。基準点は自分で作ってしまえばいいし、ハッキリ言ってベストエフォートだと思う。

「遅すぎたのではないか」という考えが生まれること自体、外部評価に囚われていて自分自身での考えが足りてない証拠だと思う。

余談 - 過程がすべて

さて「何がしたかを考えよう」と書いたが、ちょっとお酒でも飲んでみよう。 そして上記したようなことは「大まじめに実践しないこと」。

さきほどから現実というゲームはクソゲーだと何度も言っているけど、唯一クソゲーでないのが「自ら過程を楽しめる」ということ。

結局のところ「なしとげたいこと」「なりたいもの」について一生懸命になるのもいいけど、 その過程を楽しんだり、そこで得られるものに目を向けたほうが絶対にいい。 ゴールまで目をつぶって走っていると、現実というゲームは本当に意味のないものになってしまう。

とくに現実ってゲームはクソゲーだから、プレイヤーを喜ばせる要素について一切考えられていない。 レベルが上がっても「パンパカパーン」なんて言ってくれないし、上記したように「しきい値」が存在しないのでレベルが上ったかもわからない。

そういった喜びはプレイヤー自身が見つけていかなきゃいけない。 現実は何が起こるか分からないけど、それをまるっと楽しめて酒の肴にするのが、たぶん現実的に楽しい生き方なんだと思う。

まぁ、これは経験則によるところが大きのでどうでもいいことだけど、 楽しみというのは意外とその辺に転がってたりするからクソゲーも捨てたものではない。