shimada-kの日記

ソフトウェア・エンジニアのブログです

求人票における「開発経験3年」の意味

エンジニアの求人票でよく目にする「開発経験3年以上」という表記についてです。その表記の裏にはどのような意図が含まれているのか言語化してみたいと思い立ちました。

同じエンジニアであれば「あ〜、まあ3年あれば〜」みたいな大体のレベル感というか想像できるスキルセットみたいなのがあると思います。ただ、例えば人事の方だったり人材会社の方だったりにはおそらく伝わりづらい気がするので、自分なりの解釈を言葉で棚卸ししてみたいと思います。

私の解釈なので一般的な意味を持つものではありません。最初に断っておきます。

開発を業務で行なったことがない人が3年間でどんな経験をしていくのか、自分の体験も振り返りつつシミュレーションしてみます。

1年目

開発を業務でやることになった初めての年次です。研修期間を終え晴れてエンジニアとして歩み出しました。やることなすこと壁にぶち当たり今までやってきたことが一切通用しない。アマチュアとプロのレベル差を身を以て知る。そういう期間だと思います。基本的には失敗と反省を繰り返す年次でありそれは会社もある程度許容してくれます。

2年目

簡単な仕事なら問題なくこなせるようになりました。まだまだ基礎の範囲は出ません。応用をきかせながら任せられる仕事の範囲を広げてゆきます。失敗しながらも成果の出し方を習得し、関わる人も増え徐々にチームメンバーの個性、得意不得意、関わり方などが見えてきます。

3年目

エンジニアとしての仕事の進め方を理解して、基本行動は満たしていると評価を受けます。先輩として振る舞うことも求められてきます。この辺りから同期内でも差がつき始めます。2年目よりもより深く自分の成果の量と質を上げるためにどうするべきかを意識します。

以上を踏まえて3年間の開発経験から得られるスキルは以下のようなものなのではないかと思いました。

  • 業務でやってはいけないことは一通りやってきており、分別のある行動ができる
  • 自分が出来ることとできないことが判断でき、出来ることはサポート無しに独力で対応できる
  • 指揮命令系統、役割・職種間といった縦と横の人間関係を意識して立ち回れる

日本において「3年」というのは学業を修める時の基本単位だったりしますからもっと発達科学的な観点から設計された高等理論があるのかもしれませんが、社会人のエンジニアとして3年間で得られることとしては上記の感じな気がしています。一旦これを「開発経験3年スキル」と呼ぶことにします。

話を振り返って例えば「開発経験3年」というものに「Webサービスの」という接頭語がついていたとすると、例えば開発経験3年スキルの1番目でいえば本番環境のデータを勝手に変更してしまい障害を起こした経験とか、gitで安易なpush -fをしてしまいみんなに迷惑をかけてしまった経験とか、そういう痛い思いを経て勉強してきたんだろう、という推測ができたりするのかと思います。

開発経験3年スキルは人によって色々な解釈があると思います。私も仕事でエンジニアじゃない人にエンジニアのスキルについて説明することがありますが、こういった背景を説明できるかどうかは共通のスキルイメージを持つために必要なことな気がします。ただし意味を細分化しすぎると解釈の余地が狭くなりますのでそれはそれで弊害がありそうな気がします。

Djangoで例外処理の共通モジュールを考えてみる

この記事は「Django Advent Calendar 2019」の9日目の記事として書かれました。

qiita.com

仕事で絶賛Djangoを使っていますので、今回記事を担当させていただきます。

ところでDjangoで共通のエラー処理ってどう対応していますでしょうか? HTTPのステータスコードの仕分けはどちらかというと枝葉な気がしていて。アプリケーションの至る所で組み込み例外をraiseするのはイケてないのではと思います。

ロジックによって投げるべき例外が違うでしょうし、投げられた例外に応じてユーザに見せるべき内容も違うはずなので、アプリケーションで例外を受けてHTTPステータスを振り分け(またはハンドラの管理)をするレイヤーが必要であろうと思います。

そもそもアプリケーションで発生するエラーはどのような区分けが適切なのでしょうか。まずはそこから考える必要があります。Djangoを使っていることから、なにかしらのWebに関するシステムを対象にしちゃっていいと思いますので、Webのシステムで発生するエラーを考えてみました。

  • ビジネス例外(処理が続行可能)
  • 技術例外(処理が続行不可能)
    • 回復可能
    • 回復不可

今回は上記のような分類をしました。「処理」というのも定義しておく必要があります。

処理:業務ロジックのこと。「続行可能性」は業務ロジック自体に組み込まれるべきかどうか。

上記の定義から「続行可能性を持たせるべき例外」というのは「その例外は業務ロジックの一部としてハンドリングされるべき」という解釈をしています。ではそれぞれについてみていきましょう。

ビジネス例外(処理が続行可能)

業務ロジック上で発生するエラーです。エラーといっても実装からみると正常系の範囲内であろうと判断されるものです。例えば商品や店舗などの検索結果が無いといった事象やログイン・ログアウトなど認証のエラー、フォームのPOSTデータが許可されていないもの(禁止文字、全角文字、ひらがな/カタカナ)など。このカテゴリでは例外は投げてはいけないものでしょう。「例外は、例外的条件に対してのみ使用すべき」1

技術例外(処理が続行不可能)

ビジネス例外と比較して、技術的な理由により引き起こされる例外であるという枠組みです。ゆえに業務ロジック自体には組み込むのはふさわしくないものです。この枠組みの中では実装により回復可能なものと、システム原因でありどうしようもないものが回復不可能なものにそれぞれ分類されると思います。

回復可能

セッションデータや日付日時系の選択が長時間経過することで時間切れになった「コンテキストの期限切れ」、サービス内の内部リンクからではなくURLを直接叩いてアクセスするような「規定ルート以外のリクエスト」、あとはAPI系でよくみる「一時的なネットワーク不通」によるリトライ処理など。例をあげるならそのようなものかなと。いうなれば実装上のガイドがあればシステムとしての完全性はより高まるものの、100%網羅されるべきものとまでは言えないもの、でしょうか。

回復不可

OSやハードウェア(あまりないかも)関連、ネットワークやファイルシステムなどのIO関連、DBの不整合、セキュリティ的に許可されていないリクエストなどが該当するかと。これはもう実装いかんでどうこうできる問題ではなく、どちらかというといち早くシステム管理者に通知を投げて対処されないといけないものであると言えます。

上記の例外分類と最終的にユーザへの見せ方を繋げているロジックと、ハンドラーの実行を管理するクラスです。

import logging
import requests
from django.http import Http404

logger = logging.getLogger(__name__)


class SystemLogicError(Exception):
    def __init__(self, log_prefix):
        self.log_prefix = log_prefix

    def log(self, message):
        logger.exception(self.log_prefix + ':' + message)


class RecoverableError(SystemLogicError):
    def __init__(self, log_prefix, callback):
        self.log_prefix = log_prefix
        self.callback = callback

    def handler(self):
        return self.callback()


class UnrecoverableError(SystemLogicError):
    def finalize(self, func):
        func()


def exception(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except RecoverableError as e:
            e.log()
            return e.handler()
        except UnrecoverableError as e:
            e.log()
            e.finalize()
            raise Http404()
        except Exception:
            raise Http404()

    return wrapper

raiseする側はexceptionをデコレータとして利用します。例としてAPI接続を試みるも、一時的なネットワーク不定によりリトライする場合の処理を上記の仕組みを利用して記述してみます。

♯ 本来リトライはrequests.retryを使うべきでしょうが、一旦サンプルとして。

@exception
def connect_api(*args):
    response_body = {}
    endpoint = 'https://www.google.com/xxx'

    def retries(num=5, host=endpoint):
        def callback():
            nonlocal num
            nonlocal host
            r = {}
            for i in range(num):
                r = requests.get(host)
                if r.status_code == requests.codes.ok:
                    break
                if i == num - 1:
                    raise ConnectionError('サーバ接続エラー')   # ここで投げるのは下位レイヤーの例外
            return r
        return callback

    closure = retries(5, endpoint)

    response_body = requests.get(endpoint)
    if response_body.status_code == requests.codes.ok:
        return response_body
    else:
        raise RecoverableError('ネットワーク接続リトライ:', closure)

https://www.google.com/xxx」という架空のエンドポイントに向けてリクエストを投げて、statu_codeが200でない場合はRecoverableErrorをclosureと一緒にraiseします。closureは5回再送して最終的にエラー状態から回復しない場合はConnectionErrorをraiseします。

回復可能な例外の場合はクロージャーを渡して返り値も受け取れるようにはしてみましたが、少し複雑になってしまった気がします。ただ例外を一元管理できれば、get_object_or_404とかは使わずにいけると思います。

今年も無事アドベントカレンダーを書くことができました。こういった自分自身が直接技術に関わる機会を大切にしていきたいと思います。

pytestのfixtureについて

この記事は「LIFULL Advent Calendar 2018」5日目の記事として書かれました。

f:id:shimada-k:20181205201906p:plain

qiita.com

今年もアドベントカレンダーの季節になりました。年々ブログを書くという行為から遠ざかっている気がします。勤務先の会社がLIFULLグループではなくなる予定なので「LIFULL Advent Calendar」の参加も今年が最後になります。来年は組織のアドベントカレンダーではなく特定の技術のものに参加することになるでしょう。

仕事でpytestを使ってテストコードを書いているのでpytestの便利な機能について紹介したいと思います。

はじめに

Python標準のunittestや他のテストフレームワークとの大きな違いはfixtureが強力というところだとと思います。unittestではsetUpClassやtearDownClassを使用することになります。

pytestではもともとfuncargという仕組みがあり、テストコード共通で使う変数を定義することができました。Pytest-2.3からより汎用的な仕組みとして fixtureが導入されました。

fixtureは高機能でテストケースで使用する変数を定義すること以外にもマニュアルに目を通すと他にも柔軟な使い方ができそうなのでいくつか取り上げたいと思います。

pytest-fixture

https://docs.pytest.org/en/latest/fixture.htmldocs.pytest.org

pytestのfixtureは依存性の注入です。セッション、モジュール、クラス、ファンクションとスコープを設定できます。 本家のドキュメントを見ているといくつか有用そうな使用について紹介します。ソースは全て引用です。

ファクトリパターン

@pytest.fixture
def make_customer_record():

    def _make_customer_record(name):
        return {
            "name": name,
            "orders": []
        }

    return _make_customer_record


def test_customer_records(make_customer_record):
    customer_1 = make_customer_record("Lisa")
    customer_2 = make_customer_record("Mike")
    customer_3 = make_customer_record("Meredith")

fixtureを複数定義するほどではないが、共通部分が多いという場合に使用できそうです。

複数パラメータ

# content of conftest.py
import pytest
import smtplib

@pytest.fixture(scope="module",
                params=["smtp.gmail.com", "mail.python.org"])
def smtp_connection(request):
    smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
    yield smtp_connection
    print("finalizing %s" % smtp_connection)
    smtp_connection.close()

parametrizeライブラリと似た様な感じですが、parametrizeはfixtureとは別の仕組みとして提供されるので、conftest.pyに書きたいならfixtureのparamsを、テストコード側で定義したいならparametrizeといった使い分けかなと思います。

fixture内でのオブジェクト返却

# content of test_appsetup.py

import pytest

class App(object):
    def __init__(self, smtp_connection):
        self.smtp_connection = smtp_connection

@pytest.fixture(scope="module")
def app(smtp_connection):
    return App(smtp_connection)

def test_smtp_connection_exists(app):
    assert app.smtp_connection

フィックスチャとして利用したいものが単純な変数やデータではなくオブジェクトになる場合はこの様な使用になると思います。ファクトリパターンのもっと構造化されたものという感じでしょうか。

テストケース内のパラメータ読み取り

# content of test_anothersmtp.py

smtpserver = "mail.python.org"  # will be read by smtp fixture

def test_showhelo(smtp_connection):
    assert 0, smtp_connection.helo()
# content of conftest.py
import pytest
import smtplib

@pytest.fixture(scope="module")
def smtp_connection(request):
    server = getattr(request.module, "smtpserver", "smtp.gmail.com")
    smtp_connection = smtplib.SMTP(server, 587, timeout=5)
    yield smtp_connection
    print("finalizing %s (%s)" % (smtp_connection, server))
    smtp_connection.close()

テストケース内でフィックスチャに渡すデータを定義するケースはぱっと思いつきませんが、fixture内でオブジェクトを返却するケースで、クラスを統一したいというケースかなと思います。「fixture内でのオブジェクト返却」と「ファクトリパターン」の組み合わせになるかと思います。

まとめ

Pytestはfixtureが充実しているのでsetup/teardownをモジュールごとに書く必要がなくなっています。そのことでテストコードの可読性が高いです。

またディレクトリごとにconftest.pyを配置することでシンプルながらも、オーバーライドも可能となっており柔軟性も高いです。

fixture以外にも実は知っていればもっと効率的にテストコードを書けるということがあると思うので、一度マニュアルに目を通すと発見があると思います。

DIYのすゝめ

11月頃からクリスマス用に子供のおもちゃを作ろうと決意し準備を進めておりました。ものとしてはAdafruit Touch Hatを使用したおもちゃを製作しました(この関連で別postで組み込み系の記事を公開しました)。

Adafruit Touch Hatは12個の静電容量式センサがついているので銅箔ステッカーを板にはりつけて触ると音がでるおもちゃです。

当初Adafruit Touch Hatということでガチのドラムマシーンを作ろうかと思っていましたが諸事情により途中で設計からやりなおすことにしました。一通りやってみてハードルををいくつか感じました。

まずハードウェアは作り直しが効きません。のこぎりで切ったらもとに戻せませんし、釘がずれて板が割れても元に戻せません。ソフトウェアのように動かしてダメだったらデバッグして製品としての精度を高めるということが通用しない、ということを痛感しました。

また、不確定要素が多かったなと思います。部材の購入はネット通販でまかなっていたのですが、これがなんともわかりにくい。製品の写真と10cm x 15cm x 10cmという非常にざっくりとした表記しかありません。「どこからどこ」がどの長さなのかちゃんと明記しているサイトはほとんどありません。なので届くまで実際の取り付け感覚は得られないです。ネット通販の場合。

あとは根本的なところなんですが、本来の用途と違う使い方は鬼門なんですね。まあ当然と言えば当然なんですが。高品質なものほど特定の用途に特化しているので別の用途で転用しようとすると途端に融通が効きません。そういう意味で見た目で良さそうと思っても実際部材として使ってみると全然ダメだったりします。

今回身にしみて以下の3点心に刻みました。

  • 部材などの買い出しは通勤沿線で行うこと
  • 買い出しをする店はできる限り安価な価格帯の店にすること
  • 設計はとにかくシンプルに、複雑さは一切排除すること

部材などの買い出しは通勤沿線で行うこと

web通販は先述のようなデメリットがありますので実際手にとって納得できたものを買った方がよいです。実際組み上げる過程で本当にいろんな問題に遭遇します。言い方を変えると組み上げるまで何が起こるかわからないです。なので必然的に何度も店に足を運ぶことになります。その時に沿線から遠い店に行くとなるとその移動時間分仕事やプライベートで都合をつけないといけなかったり、交通費が別途かかったりします。その労力たるやバカになりません。

買い出しをする店はできる限り安価な価格帯の店にすること

購入した部材が残念ながら無駄になってしまった時に5000円で買った部材と200円で購入した部材だとどうなるでしょうか。前者はきっと諦めきれないでしょう。またもう一度店に行くのも非常に億劫です。しかし安ければ安いほど残念な結果が出ても飲み込みやすいです。ベストは100円ショップです。100円ショップで買える範囲内で設計することをおすすめします。

設計はとにかくシンプルに、複雑さは一切排除すること

これはソフトウェアも同じですが複雑な設計はあらゆる問題の温床になります。ことDIY初心者は極端なほどシンプルさにはこだわった方がいいと思います。余計な加工や実装上のギミックなどは(考えるのは楽しいですが)極力我慢しましょう。シンプルな設計にするとそこから応用を広げていくことも可能です。ハードはユーザの使い方次第で工夫の余地がありまる。そこはソフトウェアと違い画一的ではありません。言うは易しするは難し。迷ったらシンプルに。迷ったらシンプルに、です。

おわりに

当初考えていたものとは全然別のかたちになりましたが今回色々勉強になりました。プロダクトとしてビジョンがあって、子供向けのおもちゃとして成立すること、設計から全体を作るのは初めてだったこと。変数がいくつもあるなかで何らかの形にする必要がありました。

結果的にうちの子は楽しそうに遊んでくれたのでよかったです。作り手冥利に尽きますね。

Raspberry Piでドラムボードを試す

この記事は「LIFULL Advent Calendar 2017」12日目の記事として書かれました。

qiita.com

年の瀬ですね。年末です。もう今年もアドベントカレンダーの季節です。

ついこの間元旦だったような気さえしますよ。本当に時間の経過は早いものです。

はじめに

仕事でPythonを使うことになったので何かいいおもちゃはないかと思ってこれを期にRaspberry Piを買っていじってみることにしました。

Raspberry Piは言わずと知れた組み込み系のハードウェアですね。OSをMicro SDに焼いてそこから起動しますね。はい、ご存知の通りです。

Raspberry Piはそれ単体だとただの小さいコンピュータなので、何かデバイスと組み合わせてごにょごにょして遊ぶんですが。音が出るボードを2品ほど試してみたのでそのレポートになります。

使用したのはDrum HATとAdafruit Touch Hatの2つの静電容量式のセンサーボードです。

Drum HAT

www.switch-science.com

白い枠の内側部分がセンサーになっており白色LEDもコードで制御することができます。センサーの数は8つです。 「Drum HAT」だけあってドラムの構成楽器のモチーフが基盤にプリントしてあります。「海賊ロボ忍者さる」はよくわかりません。

f:id:shimada-k:20171212004643p:plain

ライブラリがわかりやすくマニュアルが充実しているので普通のエンジニアなら難なく動かせると思います。ただ残念なのが、むき出しで使うことが前提になっているようでセンサー部分の拡張性が全く考慮されておりません。

Adafruit Touch Hat

www.adafruit.com

こちらはタッチ部分が2穴になっていて、公式サイトのようにワニ型クリップで挟むことができるようになっています。センサーは12個です。

f:id:shimada-k:20171212004700p:plain

Drum HATの欠点だった拡張性が確保されていて基本的には触るモノ(もちろん導電性があるもの)をボードに接続することが前提になっています。

ただこちら、実は届いてそのままは使えませんでした。GPIOソケットと本体が別れた状態になっていて、はんだ付けが必要になります。

f:id:shimada-k:20171212005312p:plain

半田付けなんて中学か高校の技術の授業でやったっきりでした。ほんとうに久しぶりでした。そのためにはんだごてを購入しました。はんだごてって意外に安いものなんだということを知りました(800円程度)。

ライブラリについて

Drum HATの方はハードウェア部分は隠蔽化されていて使いやすいライブラリになっている感触です。プログラムを書く側はセンサー部分が反応した時のコールバックインターフェースの実装に集中できます。

Drum HATのサンプルプログラムを改良しクラス化して他のモジュールを組み込みやすいようにしてみました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import glob
import os
import time
import drumhat
import pygame

import signal

from pprint import pprint

import sys

class TestDrum:
    #DUM_FOLDER = ""
    #files = None
    #samples = None

    def __init__(self, source):
        DRUM_FOLDER = source
    
        BANK = os.path.join(os.path.dirname(__file__), DRUM_FOLDER)
        pygame.mixer.init(44100, -16, 1, 512)
        pygame.mixer.set_num_channels(16)

        self.files = glob.glob(os.path.join(BANK, "*.wav"))
        self.files.sort()
        self.samples = [pygame.mixer.Sound(f) for f in self.files]

    def start_record(self):
        for i in [0, 1, 2, 3, 4, 5, 6, 7]:
            drumhat.led_on(i + 1)
            pygame.time.wait(100);
            drumhat.led_off(i + 1)

td = TestDrum("drums")
td.start_record()

def handle_hit(event):
    td.samples[event.channel].play(loops = 0)
    print("You hit pad {}, playing: {}".format(event.pad, td.files[event.channel]))
    td.hit_time = time.time()

def handle_release():
    pass

drumhat.on_hit(drumhat.PADS, handle_hit)
drumhat.on_release(drumhat.PADS, handle_release)

# シグナルハンドラ
def handler(signal, frame):
    # ここで終了処理を行う
    sys.exit(0)

signal.signal(signal.SIGINT, handler)

while(True):
    time.sleep(1)

一方Adafruitの方はDrum HATよりも一つ下のハードウェア層をいじらないといけないようなイメージです。サンプルを見ると自前で論理演算をやってどのセンサーが反応したかをメインループの中で判定していたりします。

github.com

まとめ

拡張性があるのは断然Adafruitの方です。基盤を直接触れさせたくないですしセンサー部分を基盤から切り離せるのは大きいです。Drum HATの方はアルミホイルを細長く切って繋いでみたり導電スポンジを切って接合部分に繋いでみたりしましたが残念ながら安定しませんでした。

pimoroniはこのDrum HAT以外にもPiano HATやRainbow HATやUnicorn HATなど色々なHATを販売しています。いろいろな組み込みプログラミングが安価で試せるのは素晴らしいですね。

Adafruitはボード自体はしっかりしてますが、ソフトウェアを頑張って作る必要があります。その分拡張性があるボートと12個のセンサーがありますので、仮にドラムの打楽器として8つ使ったとしても残り4つは制御用など別用途で使用できます。センサー部に導線を固定できる機構もありますのでネジとボルトで固定できます。

どちらも使い方次第で夢が広がるボードです。実際にモノを動作させるプログラムを書くのは新鮮で楽しいものです。

IBM Blockchainでソリッドステートを作る

この記事は「ネクスト(Lifull) Advent Calendar 2016」21日目の記事として書かれました。

qiita.com

前回の投稿からかなり時間が空いてしまいました。今年も年の瀬ですね。

なぜ年の「瀬」というかというと、「瀬」には「流れの速い」という意味があるそうです。またツケ払いが多かった江戸時代には年末にツケの清算がなされたことから、めまぐるしい時期ということで年末のことを「年の瀬」ということになったらしいです。

はじめに

私はFinTech系の事業に携わっておりますので、ぜひブロックチェーンについて議論したいと思いました。

昨年にはマウントゴックスの事件などありキナ臭い印象が広がっているかもしれません。しかしマウントゴックスの破綻とブロックチェーンの技術自体には何の因果関係もありません。

2016年には三菱UFJが仮想通貨に参入したり、2017年度の税制改正ではビットコインの購入に際しては消費税を課税しないなど、ブロックチェーンを使用した仮想通貨は徐々に社会的な認知度をあげていると言えます。

闇雲にブロックチェーンについてレポートを書いてみるものいまいちモチベーションが上がりませんので、今回は「攻殻機動隊 S.A.C. SSS」のソリッドステートを取り上げてみることにしてみました。

フライト記録は改竄しておけよ

最低限の説明は必要でしょう。「攻殻機動隊 S.A.C. SSS」は2011年に公開された劇場版・攻殻機動隊です。「攻殻機動隊 S.A.C. 2nd GIG」で草薙素子が9課を去ってから2年後が舞台となっており、少子高齢化社会を背景にして新たな組織となった9課の苦悩と、超ウィザード級ハッカー傀儡廻(くぐつまわし)」との電子戦が描かれています。

sss.ph9.jp

ソリッドステートとは全自動老人介護システム内の誘拐インフラのことです。虐待を受けている子供を身寄りのない老人の子供としてデータを書き換えるシステムを指しています。

f:id:shimada-k:20161220232356p:plain

少子高齢化時代の対策として、厚生労働省の職員が開発したシステムとされています。

少子化が叫ばれるなか、年間5万人もの6歳未満の子供が無意味に命を落としていることを。

児童相談所や警察など関係機関が事態を把握していたにもかかわらず防ぐことができなかったものだ。

どこかで聞いたような話です。2011年の作品ながら、現在を言い当てているようです。

ソリッドステートの本質は記憶を改竄することだと思います。なにせ子供の親を入れ替えないといけないのですから。親と子供の記憶は最低限書き換えが必要ですし、親類やその他周辺関係者の記憶も書き換える必要があるでしょう。

警告を無視したな

さて、本題に入りましょう。ブロックチェーンを使った仕組みを作るということは、チェーンコードを書くということです。今回はIBM Blockchainを使います。

IBM BlockchainとはIBM Bluemix上で動かせるアプリケーションフレームワークです。GUIで操作できAPIを叩いてデプロイできます。IBM BluemixとはIBMが提供するPaaSです。30日間の無料トライアル期間があります。

IBM Bluemix http://www.ibm.com/developerworks/jp/bluemix/

IBM Blockchainのサービスでは4つのピアを動かすことができます。

f:id:shimada-k:20161220234608p:plain

IBM Blockchainで自作のチェーンコードを動かす場合、APIメニューを使用することになります。

f:id:shimada-k:20161220234618p:plain

APIメニューではリクエストを送信して検証するためのUIを提供しています。curlを使って自前でリクエストすることもできますが、開発時はこちらを使ったほうが格段にはかどります。

委ねてみるわ ゴーストの囁きに

チェーンコード

IBM Blockchainでは Hyperledger Fabricというブロクチェーンを基盤に採用しています。本家のHyperledgerプロジェクトから派生したインキュベーションプロジェクト、という位置づけです。オープンソースで公開されています。

Hyperledger Project https://www.hyperledger.org/

HyperledgerではDeploy,Invoke,Queryの3種類のメソッドを使います。Deployはデプロイ時に使用するもの、Invokeはデータの新規追加、更新、削除。Queryは参照です。

今回はInvokeでデータを書き換え、他のピアでもデータが書き換わっていることをQueryで確認します。

IBM Blockchain上で動いている4つのピアは個々人の記憶の塊だと見立てます。

記憶のデータは以下のような構造にしました。戸籍データのようなものです。

type Family struct{
    Sex int `json:"sex"`
    Birthday string `json:"birthday"`
    SpouseId string `json:"spouse_id"`
    FatherId string `json:"father_id"`
    MotherId string `json:"mother_id"`
    ChildId string `json:"child_id"`
}

データの改竄フローは以下のように実装しました。

  1. 父の持つ子供IDを消す(hospital)
  2. 母の持つ子供IDを消す(hospital)
  3. 子供の両親IDを消す(plugged)
  4. 子供の父親IDを老人IDに変更する(adopted)

ちなみに「攻殻機動隊 S.A.C. SSS」では老人は理解した上でソリッドステートに加担していたので、作品中では明確にされていませんが老人は自分の子だとは認識していなかったと思われます。そのため老人のデータ(記憶)は変更しないことにします。

実装したチェーンコードはgistにあげておきました。

動かしてみる

下記のリスクエストでデプロイします。IBM BlockchainのAPIメニューから行います。ピアは0を指定しています。ユーザはuser_type1_0でログインしています。

 {
     "jsonrpc": "2.0",
     "method": "deploy",
     "params": {
         "type": 1,
         "chaincodeID": {
             "path": "https://github.com/shimada-k/learn-chaincode/finished"
         },
         "ctorMsg": {
             "function": "init",
             "args": [
                 "hello"
             ]
         },
         "secureContext": "user_type1_0"
     },
     "id": 0
 }

データを準備する

Invoke:addを使用してデータを追加します。あらかじめ老人、子供、子供の父母のデータを作成しておきました。

老人

老人は80歳という設定です。身寄りのない老人ということで配偶者と子供はありません。

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\": 1,\"birthday\": \"1936-12-21\",\"spouse_id\": \"\",\"father_id\": \"8b77fb747eda782e202f85f3c7e5df850200977a053527fccbc3914d50ee98b5\",\"mother_id\": \"194f138b39f10252c32bd1cc95b9144da7688954c56eb014c3b7c4759ce33735\",\"child_id\": \"\"}"
  },
  "id": 0
}
子供の父

父親は38歳(1978年生まれ)です。

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\": 1,\"birthday\": \"1978-02-20\",\"spouse_id\": \"57001488396447b13c743ec4b3e6f7c6f01a53022c7141a8188d6677bf599fa4\",\"father_id\": \"786580bfbb1477f9f00c2bb3566ad52f204067d52d2756a9b4ea408ffbe74152\",\"mother_id\": \"e81727115da92778c19055bd2839bf6c3105224062958e28d7d2317de8a7cdd6\",\"child_id\": \"65152f7fb723e1f385bab62bdc18673209e2324f356f895bf516365cc62834e2\"}"
  },
  "id": 0
}
子供の母

母親は36歳です。sex:1は男性、sex:2は女性としています。

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\": 2,\"birthday\": \"1980-08-14\",\"spouse_id\": \"7b451b73ce0614147b01edb102f318a7496ebb533dde28e1fd80c370770f6279\",\"father_id\": \"ff5b8a198f9f96f89ca2aa483c927d58ab7f42563df7c24b47992fdf3cddccea\",\"mother_id\": \"c2dff7c37bbc245dc93ce572476254bd287860e1cebd2b5f36181748fd1f5a54\",\"child_id\": \"65152f7fb723e1f385bab62bdc18673209e2324f356f895bf516365cc62834e2\"}"
  },
  "id": 0
}
子供

子供は6歳です。当然ながら配偶者と子供はいません。father_idとmother_idはすでに追加したIDを登録しています。

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\": 1,\"birthday\": \"2010-11-03\",\"spouse_id\": \"\",\"father_id\": \"7b451b73ce0614147b01edb102f318a7496ebb533dde28e1fd80c370770f6279\",\"mother_id\": \"57001488396447b13c743ec4b3e6f7c6f01a53022c7141a8188d6677bf599fa4\",\"child_id\": \"\"}"
  },
  "id": 0
}

データが揃ったのでいよいよ理不尽に損なわれてしまうゴーストの再利用を一部始終、追体験してみましょう。 Invokeでデータを書き換え、Queryで書き換わったことを確認します。

まずはデータの書き換えからです。リクエストするJSONを示します。

父の持つ子供IDを消す(hospital)

{
  "jsonrpc": "2.0",
  "method": "invoke",
  "params": {
    "type": 1,
    "chaincodeID": {
      "name": "a027a646d118ca8513ae6854ca455ab6d3c1bf0c6ce331dfc384a7a406605114e2be6c7c46abbb3aaec8986a93327b8dbb5b2c399b3a7ff02eea48dac7184e74"
    },
    "ctorMsg": {
      "function": "hospital",
      "args": [
        "7b451b73ce0614147b01edb102f318a7496ebb533dde28e1fd80c370770f6279"
      ]
    },
    "secureContext": "user_type1_0"
  },
  "id": 0
}

母の持つ子供IDを消す(hospital)

{
  "jsonrpc": "2.0",
  "method": "invoke",
  "params": {
    "type": 1,
    "chaincodeID": {
      "name": "a027a646d118ca8513ae6854ca455ab6d3c1bf0c6ce331dfc384a7a406605114e2be6c7c46abbb3aaec8986a93327b8dbb5b2c399b3a7ff02eea48dac7184e74"
    },
    "ctorMsg": {
      "function": "hospital",
      "args": [
        "57001488396447b13c743ec4b3e6f7c6f01a53022c7141a8188d6677bf599fa4"
      ]
    },
    "secureContext": "user_type1_0"
  },
  "id": 0
}

子供の両親IDを消す(plugged)

{
  "jsonrpc": "2.0",
  "method": "invoke",
  "params": {
    "type": 1,
    "chaincodeID": {
      "name": "a027a646d118ca8513ae6854ca455ab6d3c1bf0c6ce331dfc384a7a406605114e2be6c7c46abbb3aaec8986a93327b8dbb5b2c399b3a7ff02eea48dac7184e74"
    },
    "ctorMsg": {
      "function": "plugged",
      "args": [
        "65152f7fb723e1f385bab62bdc18673209e2324f356f895bf516365cc62834e2"
      ]
    },
    "secureContext": "user_type1_0"
  },
  "id": 0
}

子供の父親IDを老人IDに変更する(adopted)

{
  "jsonrpc": "2.0",
  "method": "invoke",
  "params": {
    "type": 1,
    "chaincodeID": {
      "name": "a027a646d118ca8513ae6854ca455ab6d3c1bf0c6ce331dfc384a7a406605114e2be6c7c46abbb3aaec8986a93327b8dbb5b2c399b3a7ff02eea48dac7184e74"
    },
    "ctorMsg": {
      "function": "adopted",
      "args": [
        "65152f7fb723e1f385bab62bdc18673209e2324f356f895bf516365cc62834e2",
        "2d9dc1dcf4330422f5594f0325b01b9326df7554c58f34a29e51a43970d5bbbd"
      ]
    },
    "secureContext": "user_type1_0"
  },
  "id": 0
}

次にデータが書き換わったことをQueryで確認しましょう。

両親のデータを確認する

父親
{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\":1,\"birthday\":\"1978-02-20\",\"spouse_id\":\"57001488396447b13c743ec4b3e6f7c6f01a53022c7141a8188d6677bf599fa4\",\"father_id\":\"786580bfbb1477f9f00c2bb3566ad52f204067d52d2756a9b4ea408ffbe74152\",\"mother_id\":\"e81727115da92778c19055bd2839bf6c3105224062958e28d7d2317de8a7cdd6\",\"child_id\":\"\"}"
  },
  "id": 0
}

子供IDが消えていますね。

母親のデータ
{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\":2,\"birthday\":\"1980-08-14\",\"spouse_id\":\"7b451b73ce0614147b01edb102f318a7496ebb533dde28e1fd80c370770f6279\",\"father_id\":\"ff5b8a198f9f96f89ca2aa483c927d58ab7f42563df7c24b47992fdf3cddccea\",\"mother_id\":\"c2dff7c37bbc245dc93ce572476254bd287860e1cebd2b5f36181748fd1f5a54\",\"child_id\":\"\"}"
  },
  "id": 0
}

こちらも子供IDが無くなっています。

子供のデータを確認する

子供のデータを確認してみましょう。父親が老人のIDに書き換わっているはずです。

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\":1,\"birthday\":\"2010-11-03\",\"spouse_id\":\"\",\"father_id\":\"2d9dc1dcf4330422f5594f0325b01b9326df7554c58f34a29e51a43970d5bbbd\",\"mother_id\":\"\",\"child_id\":\"\"}"
  },
  "id": 0
}

いいですね。父親IDが老人のIDに書き換わり、母親IDは空です。

別のピアで確認してみる

ブロックチェーンですから、他のピア上でも同一のデータ処理が行われているはずです。

上述の処理はピア0で行っているのでピア1でも確認してみます。APIメニューの上部でピアを切り替え、registerでuser_type2_0でログインしてQueryしてみましょう。

父親のデータ

request

{
  "jsonrpc": "2.0",
  "method": "query",
  "params": {
    "type": 1,
    "chaincodeID": {
      "name": "a027a646d118ca8513ae6854ca455ab6d3c1bf0c6ce331dfc384a7a406605114e2be6c7c46abbb3aaec8986a93327b8dbb5b2c399b3a7ff02eea48dac7184e74"
    },
    "ctorMsg": {
      "function": "read",
      "args": [
        "7b451b73ce0614147b01edb102f318a7496ebb533dde28e1fd80c370770f6279"
      ]
    },
    "secureContext": "user_type2_0"
  },
  "id": 0
}

response

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\":1,\"birthday\":\"1978-02-20\",\"spouse_id\":\"57001488396447b13c743ec4b3e6f7c6f01a53022c7141a8188d6677bf599fa4\",\"father_id\":\"786580bfbb1477f9f00c2bb3566ad52f204067d52d2756a9b4ea408ffbe74152\",\"mother_id\":\"e81727115da92778c19055bd2839bf6c3105224062958e28d7d2317de8a7cdd6\",\"child_id\":\"\"}"
  },
  "id": 0
}

・子供のデータ

request

{
  "jsonrpc": "2.0",
  "method": "query",
  "params": {
    "type": 1,
    "chaincodeID": {
      "name": "a027a646d118ca8513ae6854ca455ab6d3c1bf0c6ce331dfc384a7a406605114e2be6c7c46abbb3aaec8986a93327b8dbb5b2c399b3a7ff02eea48dac7184e74"
    },
    "ctorMsg": {
      "function": "read",
      "args": [
        "65152f7fb723e1f385bab62bdc18673209e2324f356f895bf516365cc62834e2"
      ]
    },
    "secureContext": "user_type2_0"
  },
  "id": 0
}

response

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\":1,\"birthday\":\"2010-11-03\",\"spouse_id\":\"\",\"father_id\":\"2d9dc1dcf4330422f5594f0325b01b9326df7554c58f34a29e51a43970d5bbbd\",\"mother_id\":\"\",\"child_id\":\"\"}"
  },
  "id": 0
}

ピア1でも同一のデータが得られました。これで他のピアでも同期が取れていることが分かりました。これで記憶の改竄がおこなれ、無事に誘拐が完了されました。

攻殻機動隊 S.A.C. SSS」においても、カ・ルマのアジトにいた子供達を見て、ボーマが「記憶の一部が消されていて、IDが別の物に書き換えられているらしい事も分かった。」と言っています。

おわりに

今回はIBM Blockchainを使用してブロックチェーンを利用した仕組みを実装してみました。ハマったところをいくつか書いておきます。

開発はOSX環境で行いましたが、home brewでインストールしたGoのバージョンが古くてビルドできなくて悩みました。IBMのサンプルプロジェクトではGo 1.6推奨と書かれていますがOSX Sierraでは1.6でもコンパイルできませんでした。最新版を入れた方が手戻りが少ないかもしれません。

IBMのサンプルプロジェクトではビルドの環境も作っていますが、チェーンコードをデプロイするだけであれば不要です。ただデプロイして動かなかった場合に切り分けするために、ビルド環境を作りデプロイ前にビルドすることを強くお勧めします。

またデプロイ時のレスポンスでchaincodeIDが帰ってくるのですが、これが毎回同じだとデプロイでチェーンコードが変化していないことになります。

原因は/finishedの中身を更新していなかったことです。/startの中にスケルトンコードが入っているのでその中をひたすらcommit & pushしていたのですが、それでは当然ながら/finishedの中が変化していません。英語のチュートリアルを流し読みしていると気づきませんでした。

IBM Bluemixが本来有料のサービスということもありますが、今後はもっとローカライズされた情報が充実していき、より多くの人にとって敷居の低いものになっていくといいですね。

冒頭でも書きましたが、「攻殻機動隊 S.A.C. SSS」は社会問題を先取りして捉えています。2016年には「保育園落ちた日本死ね!!!*1」というブログ記事が公開され国会で話題になりました。待機児童の問題は主に都市部が深刻ですが、負担にあえぐ現役世代の声が顕在化した事象といえます。

一方高齢化問題としては、2025年問題を控えて社会保障制度の見直しが喫緊の課題です。2017年度から医療保険介護保険の見直しが入ります。年金カット法案と称されつつも国民年金法改正案*2が採決されました。

2017年はどのような年になるでしょうか。とりあえず都内に保育園が増えてくれるとありがたいですね。少子化問題社会保障制度の見直しを含めた高齢化問題は引き続き最優先事項です。

それにしても、ネットは広大だわ。もう既に、私達の知らない次の社会が生まれ始めている・・・


本記事は犯罪としての誘拐を肯定又は助長するものではありません

GCDの実行ポリシーとキューの種類まとめ

今更ながらGCDを勉強しました。Grand Central Dispatchです。わからなければ自分で使ってみることです。そうしたらいざという時に役に立ちます。

調査

GCDには主に2種類のキューのタイプと2種類のディスパッチポリシーがあります。組み合わせで合計4種類の使い方が存在します。

  1. 非同期実行&並列キュー
  2. 非同期実行&直列キュー
  3. 同期実行&並列キュー
  4. 同期実行&直列キュー

webの資料を読んでもいまいちイメージがつかなかったので以下のようなコードで4パターン検証してみました

環境

#include <stdio.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <dispatch/dispatch.h>

#define CEIL_MAX    100000
#define NUM_THREAD  20
#define CONTEXT_PRINT   1 

// 1からceilまでの間で素数の個数を計算する関数
int prime(int ceil, int thread_order)
{
    int i, j, num_yakusu, prime = 0;

    for(i = 1; i <= ceil; i++){
        num_yakusu=0;

        for(j = 1; j <= i; j++){
            if(i % j == 0){
                num_yakusu++;
            }
        }
        
        // 1とそれ自身でしか割り切れなかったら素数
        if(num_yakusu == 2){
            prime++;
            if(CONTEXT_PRINT){
                printf("thread order:%d\n", thread_order);
            }
        }
    }
    return prime;
}

// entry point
int main(int argc, char *argv[]){
    int i;

    //dispatch_queue_t q = dispatch_queue_create("gcd.sample.osx", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t q = dispatch_queue_create("gcd.sample.osx", DISPATCH_QUEUE_SERIAL);
    //dispatch_group_t group = dispatch_group_create();

    srand((unsigned)time(NULL));
    printf("loop start\n");

    for(i = 0; i < NUM_THREAD; i++){
        dispatch_group_async(group, q,
        //dispatch_sync(q,
            ^{
                pthread_t p = pthread_self();
                struct timeval tp;
                int ceil = rand() % CEIL_MAX + 1;
                int num_prime = prime(ceil, i);
                int j = i;
                struct timeval tq;

                printf("i:%d, thread id:%p ceil:%d num_prime:%d\n", i, p, ceil, num_prime);    
            }
        );
        printf("thread done\n");
    }
    //dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    printf("loop end\n");
}

1からceilまでの数値で素数がいくつあるか調べる処理をGCDを使って並列処理するコードです。

GCDの実行ポリシーとキューの種類の4パターンでソースコードを切り替え、生成されるスレッドの個数とディスパッチの対象を調べました。

パターン スレッドの個数 ディスパッチの対象
1 5 インキューされているすべてのタスク
2 2 実行中のタスクの次にインキューされたタスク
3 1 実行中のタスクの次にインキューされたタスク
4 1 実行中のタスクの次にインキューされたタスク

考察

パターン1と2は非同期実行です。1の場合は非同期実行&並列キューです。タスク同士が完全に独立で並列に実行されます。スレッドはメインスレッドの他に4スレッド生成されて、タスクすべてがコンテキストスイッチされて実行されます。

2の場合は非同期実行&直列キューです。メインスレッドの他に1つスレッドが生成されてそのスレッド内でインキューされた順番に実行されていきます。パターン1と2のケースは両方とも親スレッドとは別個にスレッドが作成されています。これは非同期実行ポリシーの特徴と言えます。

パターン3と4は同期実行です。3の場合は同期実行&並列キューです。並列キューなので複数スレッドが立ち上がると見せかけて実際起動するのは1スレッドだけです。

dispatch_syncはそれぞれのブロックの実行が完了するまで終わらないのです。なので、並列キュー的には複数スレッドを立てたいけど、処理できるブロックが1個ずつだから結果として1個しかスレッドが 立たないということになります。

4の場合は同期実行&直列キューです。これは言わずもがな純度100%の直列&同期実行です。インキューされた順番に1つ1つタスクが完了するまで次のタスクは実行されません。パターン3と4のケースは子スレッドが作成されるわけではありませんので親スレッドはブロックされます。

まとめ

実行結果より、キューの種類よりも実行ポリシーのほうが支配的であると言えます。

また「同期」と「直列」という単語が2種類出てくるのでわかりにくくなっているのですが、「同期」というのはタスク間に対して使われているものではなく、メインスレッドとの関係性を表す単語です。

そして「直列」というのはタスクの順番のみを表す単語として使用されています。少なくともGCDにおいては。

親スレッドとは別なところでCPUを全て使って複数処理を同時に走らせる(いちばんイメージしやすいパターン)なら並列&非同期の組み合わせ一択。親スレッドは自由にしたいというだけの場合は直列&非同期でも可。

むしろ上記以外のケースだった場合、GCD使ってもメリット薄い気がしますね。。

sched_setaffinityを使って非同期&並列キュー環境で全て同じCPUに固定した場合どうなるのか試したかったんですが、OSXでは使えませんでした。残念。

久しぶりにC言語を使いました。楽しかったです。