読者です 読者をやめる 読者になる 読者になる

Jの衝動書き日記

さらりーまんSEの日記でございます。

データ定義から読み取る仕様の目次

データ定義から読み取る仕様 技術

1. はじめに

2. 設計書から読み取れる仕様とは

3. 設計書が欠けると落ちていく仕様

4. 定義から仕様を読み取る手順

4.2 テーブル定義+E-R図(物理モデル)+データ

4.3 4.2 + E-R図(論理モデル)

4.4 4.3 + E-R図(概念モデル)

5. 仕様が抜け落ちないようにするための工夫

6. おまけ

Pythonで簡単にWebサービス構築-JSON編

技術

 前回、PythonWebサービス構築用フレームワークのtornadoについて紹介したが、今回はJSONに特化した拡張ライブラリ-Tornado-JSONについて紹介する。

 

Tornado-JSONのインストール

 tornadoと同様にpipを使おう。

    pip install Tornado-JSON

特徴

  • Webapiの入出力形式にJSONを採用しそれに特化することで使いやすくしている
  • パッケージ名・モジュール名で自動的にURLを決定
  • jsonschemaによるバリデーション実行をより使いやすくする
  • APIドキュメントの自動生成が可能

 

Webサービス構築手順

使用するクラス

  • from tornado.ioloop import IOLoop 
  • from tornado_json.routes import get_routes
  • from tornado_json.application import Application
  • from tornado_json.requesthandlers import APIHandler
  • from tornado_json import schema

  ※Applicationはクラス名がtornadoと一緒だがパッケージが異なる

手順

1. HTTP REquestに応答するためのHandlerを作成する。APIHandlerを継承したクラスを作成し、getやpostメソッドを実装する。

class HelloWorldHandler(APIHandler):
   
      @schema.validate(
          output_schema={"type":"string"},
      )
      def get(self):
          return "Hello world!"

 ※応答結果はreturnで返す
   ※@schema.validateで応答結果の形式を指定する必要がある

 

2. 1を入れるパッケージを作成する。ディレクトリを作成し、その中に1で作成したクラスのファイルと__init__.pyを入れる。

$ ls helloworld
__init__.py api.py

 

3. Handler探索を行う。パッケージを指定することで、その配下にあるAPIHandlerを継承したクラスを探索し自動的にURLを作成する。

   import helloworld
   routes = get_routes(helloworld)

 

4. Applicationを作成する。3.で作成したroutesを引数に指定して作成する

   settings = {"debug":True }
   Application(routes=routes, settings=settings, generate_docs=True)

  ※tornado.web.Applicationとは引数が異なるので注意!

5.  待ち受けポート番号を設定する。

app.listen(8888)

6. 接続受付を行う。Ctrl-Cしない限りは応答が返らない。

 IOLoop.current().start()

 起動するとAPIドキュメントが自動生成される。また、応答形式はapplication/jsonとなり、{"status":{実行結果} "data":{get/postのreturn内容}となる

 

ソース全体

 /home/pythonSample/tornado-json
|--helloworld
|  |--api.py
|  |--__init__.py
|--server.py


<server.py>
from tornado.ioloop import IOLoop
from tornado_json.routes import get_routes
from tornado_json.application import Application
import json

def make_app():
    import helloworld
    routes = get_routes(helloworld)
    print("Routes\n=======\n\n" +
           json.dumps([(url, repr(rh)) for url, rh in routes], indent=2)
          )
    settings = {"debug":True }
    return Application(routes=routes, settings=settings, generate_docs=True)

if __name__ == "__main__":
   app = make_app()
   app.listen(8888)
   IOLoop.instance().start()

<api.py>
from tornado_json.requesthandlers import APIHandler
from tornado_json import schema

class HelloWorldHandler(APIHandler):

    @schema.validate(
        output_schema={"type":"string"},
    )
    def get(self):
        return "Hello world!"

 

URLの決定

  {パッケージ名}/{モジュール名}/{Handler名}となる。Handler名からはHandlerの文字列は取り除かれてすべて小文字となる。 パッケージ名はget_routes()で指定したパッケージ配下にパッケージがある場合に適用される。
 上記の例ではURLは「/api/helloworld」となる

URLパラメータの取得

  get,postに引数を追加した場合、自動的にURLパラメータを受け取る形になる。パターンはw+の形式となる。引数のパターンはHandler名の後に追加される

 class UrlParamHandler(APIHandler):
    def get(self, fname, lname):
 → /api/urlparam/(?P<fname>[a-zA-Z0-9_\\-]+)/(?P<lname>[a-zA-Z0-9_\\-]+)/?$

URLの指定

 Handler内に__url_names__を定義すると、Handler名に相当するところが置き換わる。また、Handler内に__urls__を定義するとURLが定義した値通りとなる。
 ただし、__urls__を定義した場合、__url_names__ = [] としないと自動生成されるURLも登録されてしまう。

クエリの取得

 tornado.webと同様にget_query_argumentを使用する。

POSTパラメータの取得

 jsonで受け取ることが前提となる。値は、self.body["プロパティ名"]で取得する。そのためにはschema.validateでinput_schemaを定義する必要がある。定義しないと、self.bodyで値は受け取れない。

 @schema.validate(
     input_schema={
          "type": "object",
          "properties": {
             "title": {"type": "string"},
             "body":  {"type": "string"},
             "index": {"type": "number"},
           },
           "required": ["title", "body"]
     },
     output_schema={
          "type": "object",
          "properties": {
              "message": {"type": "string"},
          }
     },
 )
 def post(self):
     return {
       "message": "{} was posted.".format(self.body["title"]),
     }

出力処理について

 各Handlerのget/postの返り値がそのまま応答値となるが、デフォルトでは以下のように加工して返している。

{ "status":"success", "data":{return値の内容}}

 これを変更したい場合は、APIHandler.success()をオーバライドする。

バリデーション

 @schema.validateを定義しておけば、指定した定義に基づいたバリデーションの実行が行われる。スキーマ定義には存在しないプロパティに関してはチェックされない(余分なパラメータがあってもエラーとはしない)。
 渡すスキーマ定義を別ファイルに定義する場合は、それを読み込むための仕組みが必要(Tornado-JSONにはない)。

エラー処理のハンドリング

 バリデーションでエラーとなった場合、デフォルトでは以下の形式で応答が返る。

 HTTP STATUS 400 Bad Request
 { "status":"fail", "data":{バリデーションエラーメッセージ}}

 また、get/post内で例外がスローされた場合はデフォルトでは以下の形式で応答が返る。

 { "status":"error", "code":{例外に応じたHTTP STATUS(通常は500)}, "message":{HTTP STATUSに応じた内容(Internal Server Errorなど), "data":{例外の内容(debug=Trueで起動した時のみ)}}

 dataに関しては、例外が属性:log_messageを持てばその内容が、持たない場合はstr()で例外を変換した内容が入る。tornado.web.HTTPErrorがこの属性を持っている。
 
 上記の応答内容を変更したい場合は、APIHandler.fail()とAPIHandler.error()をオーバライドする。
 APIHandler.fail()は例外としてjsonschema.ValidationErrorかtornado_json.exceptions.APIErrorがスローされた場合に呼ばれる。
 APIHandler.error()はそれ以外の例外がスローされた場合に呼ばれる。tornado.web.HTTPErrorはこちらで処理されることになる。
 独自で例外を作成する場合も、APIErrorを継承した方が例外の応答処理が楽に作れる。
 
 また、404応答を独自に返したい場合、Handlerを作成してApplicationのオプションdefault_handler_classで作成したHandlerを指定すればよい。特に指定しない場合はErrorHandlerが指定される。が、応答がtext/plainとなりjson形式ではないため変更するのが望ましい。

参考

Tornado-JSON — Tornado-JSON 1.2.2 documentation

上記のサンプルコードは以下にあります。

github.com

Pythonで簡単にWebサービス構築

技術

 お仕事で、Pythonのtornadoについて学んだのでそのメモを公開する。tornadoはシングルスレッドで動作する軽量なWebサービス構築用のフレームワーク。テスト用のMockサービスなどちょっとしたものならばJavaでゴリゴリ書くよりは手っ取り早く作れる。

Python環境構築

 pyenv環境を構築するのがオススメ。pyenvを使えば異なるバージョンのPythonを切り替えて使えるようになる。また、プラグインのvirtualenvを使えば同じバージョンで異なる環境(インストールされているPythonライブラリを変えるなど)を作れる。

   pyenvの管理配下に複数のpythonをインストールし、ライブラリもそれぞれそこに置かれる。

 構築方法は以下に丸投げを参照。pyenv および virtualenv の使い方 - Qoosky

    Windows? 知りません。まあ、Windows上で頑張るよりはvagrant入れてLinux環境作った方が簡単ですよ。

 

tornadoのインストール

 pythonのライブラリインストールはpipを使えば楽。

    pip install tornado

    パッケージの確認はpip list

 

Webサービス構築手順

使用するクラス

  • from tornado.ioloop import IOLoop
  • from tornado.web import RequestHandler, Application

手順

1.HTTP Requestに応答するためのHandlerを作成する。

 RequestHandlerを継承したクラスを作成し、getやpostメソッドを実装する。

class MainHandler(RequestHandler):
    def get(self):
        self.write("Hello, world")

 RequestHandler.write()で応答結果を返す。javaservletのような感じ。

 RequestHandler.render()を使用すればテンプレート化されたhtmlを返せる(jspのような感じ)

 

2.Applicationを作成する。

 引数には、urlとそれに対応するHandlerをペアにしたtupleの配列を渡す。

 定義: class tornado.web.Application(handlers=None, default_host='', transforms=None, **settings)

   Application([
        (r"/hello", MainHandler),
      ], debug=True)


   r"/hello"はraw stringというpythonの記法で制御文字が単なる文字列として扱われる
   **settingsは可変長引数の宣言でkeyword=値の形で渡される debug=Trueなど

 

3.待ち受けポート番号を設定する

app.listen(8888)

 

4.接続受付を行う。

 Ctrl-Cしない限りは応答が返らない

IOLoop.current().start()

 

ソース全体

from tornado.ioloop import IOLoop
from tornado.web import RequestHandler, Application

class MainHandler(RequestHandler):
    def get(self):
        self.write("Hello, world")

def make_app():
    return Application([
        (r"/hello", MainHandler),
        ], debug=True)

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    IOLoop.current().start()

 

URLパターンマッチング

  REST的にWebサービスを構築するならば、/user/{id}などでGETした時のidを取得したい。これは以下の手順で行えばよい。

1.Applicationを作成するときにurlを正規表現で作成する

   Application([
    (r"/user/(?P<id>[0-9]+)", UrlMatchSampleHandler),
    ...
    ])

 ?P<id>はpython拡張正規表現で、一致する箇所を<>で指定した名称で取り出せる。

 

2.対応するget/postメソッドに引数を設定する

def get(self, id):

 これで、/user/1111でGETした場合、idには1111が渡される。なお、URLパターンに一致しないアクセスは404応答となる。

 

クエリの取得

  /user?id=xxxなどのGETクエリを取得したい場合はget_query_argumentを使用する。なお、指定したパラメータが存在しない場合は例外がスローされるため注意。defaultを指定すればエラーとはならない。

id = self.get_query_argument("id", default=9999)

 

フォームの取得

 POSTで送信されたformデータを取得したい場合はget_body_argumentを使用する。

name = self.get_body_argument("name")

 

Jsonの扱い

 POSTで送信されるデータがjsonの場合は、 json_decodeで変換すればjsonデータとして扱える。なお、Webサービスjson中心となる場合はTornado-JSONを使用した方が楽。

json = json_decode(self.request.body)

 

非同期処理

 tornadoはシングルスレッドで動作するため処理に時間がかかるようなサービスがあると後続のリクエストはその処理が完了するまで待たされることになる。 それを避けるためには対象のサービスを非同期処理に対応させる必要がある。

1.非同期処理とするメソッドに@tornado.gen.coroutineを付ける

2.対象のメソッドにおいて非同期処理の前処理を書く

3.非同期処理を行う箇所にyieldを宣言する

4.yield以降にそれを受け取った処理を書く

  @gen.coroutine
  def get(self):
      http_client = AsyncHTTPClient()
      response = yield http_client.fetch("http://localhost:8888/heavy")
      self.write("respose is {} ".format(response.body))

 

 なお、yield宣言した処理内ではスレッドをブロックするような処理を書かないこと。ブロックするような処理がある場合は非同期処理とはならないので注意。例えばtime.sleepはブロック処理である。

yieldについて

 関数内にyieldがある場合、その関数は返り値としてgeneratorを返すようになる。generatorから値を取り出すにはfor in か__next()__(python3の場合)で取り出す。

 generatorにアクセスするたびにyield宣言されている箇所まで処理を進めていき、yieldのところで値を返す。

 def hoge():
   処理1
   yield "one"
   処理2
   yield "two"
   処理3
   yield "three"
 
 gen = hoge()
 gen.__next()__ → 処理1が実行されてoneが返る
 gen.__next()__ → 処理2が実行されてtwoが返る
 gen.__next()__ → 処理3が実行されてthreeが返る
 gen.__next()__ → 例外スロー

※for in gen の場合は例外はスローされない

 

非提供のもの

  • セッション

   tornado本体では未提供だが拡張版が存在する(torndsession 1.1.4 : Python Package Index)。セッション情報の保存はmemcashedかRedis上で行っている模様。

  • バリデーション

   formバリデーションはtornado本体では未提供。wtforms-tornadoというものがある。jsonに関してはtornado-jsonを使えばjsonschemaによるチェックを行える。

 

参考

 何だかんだ言って公式のサイトがわかりやすく一番参考になる。英語だけど。

 User’s guide — Tornado 4.4.1 documentation

 

 上記のサンプルコードは以下にあります。

github.com

PokemonGO!でお手軽にジムバトル

日常

 PokemonGOが日本上陸を果たしてはや二週間。いまや石を投げればポケモントレーナーに当たるという熱狂ぶりだ。まあ、投げた石は自分に当たるかもしれないが。
 それはさておき、ジムバトルのことである。日本上陸当初はポッポやヒトカゲがジムリーダーを務めるというほのぼのぶりだったが、今や2000CPクラスのポケモンがゴロゴロいるような状況だ。その中でも目に付くのがカイリューやウィンディなど強力でレア度が高いポケモン達である。ちくしょう。非常に羨ましい。夏休みが長い学生達よ! PokemonGOをやらずに宿題や課題やってろよ! こちとら平日は仕事でゲットできないんじゃ! 夜中は出現率低くなるし、真夜中の徘徊は不審者そのものだし!
 ……コホン。何か心の叫びが出てしまったがきっと気のせいだろう。
 だが、高CPのポケモンがいるだけでジムバトルを躊躇していないだろうか。どうせ勝てやしまいと。だとしたら残念なことである。非常に残念なことである。なぜかと言うとポケモンバトルはCPの高さがすべてではないからだ。重要なのは相性なのである。
 と、言うわけでいわゆるレアを見せびらかす輩をぶっ潰せ高CPにビビらずジムバトルを楽しむ方法をお話しよう。

ジムバトルのボーナスについて

 その前にジムバトルのご褒美について確認しておこう。
 自分がジムリーダー・メンバーになった場合、コインをゲットできることはご存知だろうか? 意外と知らない人が多いみたいなので一応紹介しておく。
 ジムにポケモンを配置した後に、ショップをタップすると右上に盾マークとその中に数字が見えると思う。この数字はジムに配置しているポケモン数である。そして、この盾マークをタップすると1ポケモンあたり10コインと500のほしのすながもらえるのだ。逆にこれをしないとジムに配置しても何ももらえない。
 また、一度盾マークをタップすると、次にボーナスをもらえるのは21時間経過した後である。なので、ボーナスをもらった後でジムにポケモンを配置してもすぐにはボーナスをもらえない。
 ボーナス狙いなのであれば、できるだけジムにポケモンを配置してからの方がよいのだが、他のプレーヤがそんな状況は許せないだろうからできるだけ迅速にボーナスはもらった方がいい。

ジムバトルの基礎

 自分のポケモン6体と相手のジムのポケモン達と一対一で連戦で戦う。相手のジムの名声をゼロにすればジムは落とせる。ジムリーダーを落とすと-2000、ジムメンバーを落とすと-500名声が下がる。なのでジムレベルがL3ぐらいだと相手は3体なのでこちらが有利で戦える。三回全勝すればジムは陥落だ。
 また、ジムレベルはL3ぐらいまではさくさく上げられるがそれ以上だと上がりづらい。何故かと言うと、同じチームメンバーでバトルしてジムの強化しなければならないが出せるポケモンは一体だけなのだ。しかも自分のポケモンのCPが相手よりも高すぎる場合は名声が上がりづらい。LV4の場合8000名声が必要のためLV3からだと2000名声が必要となる。なのでジムレベルを上げられる前にさっさと落としてしまおう。
 あと、げんきのかけらとキズぐすりは忘れずに。特にキズぐすりは十分な量を確保しておこう。なぜなら勝利したあとにポケモンをジムに配置するがポケモンのHPが満タンでないと配置できないのだ。なのでせっかく勝利しても配置できるポケモンがポッポだけなんて状況になってしまい、ボーナスを貰う前に速攻で落とされてしまうだろう。

ポケモンバトルの基本

 最初に述べたとおり、重要なのはCPではなく相性である。相性さえあえばCPが半分程度でも十分勝機はあるし、三倍以上離れていても戦い方次第でなんとかなる。
 相性はタイプ相性表 - ORAS・XY対応 - ポケモン徹底攻略を参照のこと。
 だが、この表はPokemonGOのものではないので若干注意が必要だ。実際のポケモンバトルでは相手の技が全く効かないということもある(じめんタイプにでんきタイプの技は効果がないなど)が、PokemonGOでは単にいまひとつになるのだ。表で言えば✕となっているところが△になる。まあ、このルールはアップデート後に変わるかもしれないので覚えて損はない(そしてポケモンバトルの世界へ行こう。サン&ムーンがいい機会ですよ?)。


 また、複合タイプの場合は弱点が組み合わせになる。
 例えばみず・ひこうの場合は次のようになる。

  •  みずはみず・こおりに強く、でんき・くさに弱い
  • ひこうはかくとう・くさ・むしに強く、いわ・こおり・でんきに弱い
  • みず・ひこうは、みず・かくとう・むしに強く、いわに弱く、でんきに非常に弱い(4倍弱点)

  特にこの4倍弱点をつけば三倍のCP相手でも逆転が可能だったりするのだ。

 と、いうわけで以下個別対応編である。基本的に相手がCP的に格上という前提で戦うため先発・本命と二段構えで戦うことを基本とする。

VS.カイリュー

 タイプはドラゴン・ひこうであるため、ドラゴン・フェアリー・いわ・こおり(4倍)が弱点である。
 攻撃技はドラゴンタイプの技が基本だが、はがねタイプ(はがねのつばさ)の技も持っている。
 ドラゴン技に耐性があるのははがねとフェアリーとなる。一方フェアリータイプははがねには弱いので優位には立てない。
 さて、攻撃としては4倍弱点であるこおり技を持つポケモンが基本となる。だが、その代表格であるラプラスはちょっと鈍いためにヒットアンドアウェイ作戦がしずらい。なのでこおり技でトドメをさせるように先発でHPを削ってから一気に仕留めるのが基本となる。もしくはカイリュー戦の前に出しておいてゲージを貯めておきカイリューが登場した時点でふぶきをぶっ放すという手もいい。


 先発:レアコイル(はがね・でんき)・ゴルダック(みず)
 本命:ラプラスパルシェンジュゴン(みず・こおり)・ギャラドス(みず・ひこう)


 でんき・はがねタイプであるレアコイルはドラゴンタイプに耐性があるので先発として優秀。しかもカイリューはがねのつばさ使いであるならばさらに耐性があるためCP差が二倍でもほぼ互角で戦える。攻撃技もでんき・はがねはカイリューに等倍なので不利にならない。上手く戦えば先発といわずレアコイル一体のみで倒せるだろう。相手のカイリューはがねのつばさ持ちであるならばサンダースでも可。
 先発の次点はゴルダックゴルダックれいとうビームを覚えるので、ヒットアンドアウェイで戦いつつれいとうビームを叩き込めば十分に先発の役割を果たせる。ただ、ゲージ技のドラゴン技は等倍なので喰らえば即死するだろうから注意が必要。


 本命はやはりラプラス。だが、カイリューの技に耐性があるわけではないので普通に戦っていたら押し負ける。ゲージ技は喰らわないように避けながら戦うこと。こおりタイプには他にはルージュラがいるが、こおりタイプははがね技に弱いのではがねのつばさ持ちのカイリューには不利になってしまう。パルシェンジュゴン候補だがHPが高くないので難易度はより高くなる。
 本命の次点はギャラドス。みずタイプなのではがねのつばさには耐性があるし、ゲージ技でりゅうのはどうを持っているならば弱点がつける。先発で削った後ならば何とか押しきれるだろう。
 さて、フェアリータイプも十分本命になるが、ルージュラと同様にはがね技が弱点なのではがねのつばさ持ちのカイリューには弱い。逆に基本技がドラゴン技ならば十分に本命の役割と果たせる。

VS.ウィンディ

 タイプはほのおタイプであるため、みず・じめん・いわが弱点である。
 攻撃技はほのおタイプが中心だが、あくタイプ(かみつく)の技も持っている。
 ほのお技に耐性があるのはみず・いわとなる。これらのタイプはほのおの弱点でもあるので苦手な相手となる。


 先発:ブースター
 本命:シャワーズゴローニャドサイドン


 先発にブースターを組み込むのはくさタイプの露払いのため。L3のジムだとほのおとくさはたいていセットで居るため、くさに有利なブースターで焼き払う。また、ウィンディの通常技がほのおかあくかを見極める役割も果たす。


 本命はシャワーズイーブイを7匹ゲットできれば手に入るためお手軽だろう(ウィンディに比べれば)。みずはほのお技に耐性があり相手の弱点にもなるためCP差二倍など余裕で跳ね返せる。次点はゴローニャドサイドンのいわ・じめんタイプ。いわもじめんもどちらもほのおには有効だし、いわはほのおに耐性があるため有利に戦える。だが、くさには4倍弱点となるためくさタイプの露払い役が必要となる。
 ちなみにこの構成は相手がブースター・ブーバーの場合でも同様に有効だ。

VS.シャワーズ

 タイプはみずタイプであるため、でんき・くさが弱点である。
 攻撃技はみずタイプの技が中心。
 みず技に耐性があるのはくさとなる。くさはみずの弱点でもあるので苦手な相手となる。

 

 先発:サンダース・ブースター
 本命:ナッシー・モンジャラ

 

 シャワーズがジムリーダーとなっている場合、むしかひこうタイプのポケモンがセットになっていることが多いため、露払いとしてむしにはブースター、ひこうにはサンダースを当てる。ブースターもサンダースもイーブイが元なので同じようにゲットしていることが多いだろう。

 

 本命はナッシーとしたが、他のくさタイプでもよい。だが、ウツボットフシギバナ・ラフネシアは進化に必要な飴が多いため、半分で済むナッシーか、不要なモンジャラが適任だろう。特にモンジャラは純粋なくさタイプであるため、基本・ゲージ技共にくさタイプであることが多く、ゲージ技もパワーウィップという強力な技を持つのでオススメである。CP700程度のモンジャラでもCP1800のシャワーズに十分対抗できる。
 また、みずはでんき技が弱点であるためサンダースでもいいが、如何せんサンダースはHPが少ない+基本技のでんきショックが5と弱いためシャワーズに押し負ける可能性が高い。

VS.カビゴン

 タイプがノーマルのため、かくとうが弱点である。
 攻撃技は、通常技がしたでなめる(ゴースト)かしねんのづつき(エスパー)で、ゲージ技がはかいこうせん・のしかかり(ノーマル)・じしん(じめん)と多彩。
 ノーマル技に耐性があるのははがね・いわであるが、カビゴンはじしんを持つのでじめん技を弱点に持ついわやはがねタイプでは優位には立てない。特にはがね・でんきであるレアコイルはじめんが4倍弱点となる。
 一方、カビゴンの弱点であるかくとう技が主体のかくとうタイプで当たるという手もあるが、通常技にしねんのづつきを持つのでエスパー技を弱点に持つかくとうタイプも優位には立てない。
 だが、カビゴンは攻撃モーションが大きく攻撃を避けやすいためCP差があっても意外と戦える。ヒットアンドアウェイ戦法でいくべし。プレーヤのスキルが試される。


 先発:カイロス
 本命:ナッシー・シャワーズ


 カイロスはむしタイプであるためじめん技に耐性があり、かくとう技も覚える。ヒットアンドアウェイに徹すれば意外と削れる。だが、かくとう技のじごくぐるまはそんなに高威力ではないためCP差がありすぎると辛い。ゲージ技のはかいこうせんに要注意。カビゴンがじしん持ちではない場合はレアコイルゴローニャサイドンでもよいが重量級対戦となるためヒットアンドアウェイ作戦には向かない。


 本命のナッシーも同様にじめんには耐性があり、エスパー技にも耐性があるため優位には立てる。先発で削っておいてゴリ押しするのがよい。だが通常技がしたでなめるだと不利となる。シャワーズはHPが高いため鈍いカビゴン相手だと優位には立てる。
 だがここは不利だと知りつつあえてエビワラーで行くのも手。ヒットアンドアウェイ戦法がボクサー姿にはよく合う。カビゴンしねんのずつきを持っていない場合にトライしてみよう。

VS.ナッシー

 タイプがくさ・エスパーのため、ほのお・ひこう・どく・ゴースト・あく・むし(4倍)が弱点。
 攻撃技は、通常技がエスパー技でゲージ技がくさかエスパー技を持つ。
 弱点が多いため簡単に倒せそうだが、エスパー技に耐性を持つのが今のところエスパーとはがねタイプだけであるため(あくタイプはカントーポケモンには居ない)、意外と苦戦する。
 エスパー技の耐性を持ち弱点をつけるポケモンは居ないためCP差がありすぎると逆転は難しい。だが同CPであるならばくさの弱点をつく相手で行けば負けることはないだろう。

 

 先発:ビショット(ノーマル・ひこう)・ブースター(ほのお)
 本命:ストライク(むし・ひこう)・カイロス(むし)


 先発でできるだけナッシーのHPを削り、本命で一気に勝負をかけるのが基本。ちなみにカイロスはかくとう技を持つものではなくむし技が主体のものを選ぶこと。
 

VS.ギャラドス

 タイプがみず・ひこうのため、いわ・でんき(4倍)が弱点。
 攻撃技はみずやドラゴンタイプが主体だが、通常技でかみつく(あく)もある。
 でんきタイプで戦うことが基本となるが、でんきタイプだとギャラドスの技に耐性はないためCP差がありすぎると一撃が痛いので要注意。


 先発:プクリン(ノーマル・フェアリー)・ピクシー(フェアリー)
 本命:サンダース(でんき)・レアコイル(はがね・でんき)


 ギャラドスの通常技はかみつく(あく)かりゅうのいぶき(ドラゴン)であるため、あくとドラゴンに耐性があるプクリンが先発には最適。だがギャラドスはドラゴンではないため弱点は付けない。
 先発で削ったらあとはサンダースのかみなりを落せば大丈夫だが、サンダースの場合基本技が弱いのでかみなりを落とす前にゲージ技を喰らわないように注意すること。サンダースは素早いため、CP1800ぐらいのギャラドスでもCP700のサンダースで十分勝機はある。

 プクリン・ピクシーがいないこともあると思うので、ギャラドス戦の前にサンダースを出してゲージを貯めておき、ギャラドスに初手で叩き込むのでも可。あとはカビゴンなどの重量級で押し切ろう。

ロクなポケモンが居ないという人へ

 さて、レアポケモンなんて見たことがないなんて人もいるだろう。ギャラドスなんてコイキングを101匹捕まえる必要があるし、ラプラスなんて姿を見たこともないということもザラだと思う。だが諦めてアプリを削除するのは待って欲しい。手はあるのだ。
 とりあえずLVを20まで上げよう。20になればハイパーボールが手にはいるようになるし、高CPのポケモンが出現しやすくなる。ポケモンの強化もLV20になるまではぐっと我慢してほしのすなを貯めておこう。
 さて、ここでロクなポケモンがいないの代表格であるコラッタ・ポッポの出番である。たくさん捕まえて飴を何匹も一気に進化する程ためたら、しあわせタマゴを使おう。そして次々と進化させるのだ。一匹あたり1000EXPが入るから20までならわりとすぐに上げられる。
 卵の孵化と合わせてもいい。時間がないという人は課金してしまおう。孵卵器の出番だ。9個の卵を孵化寸前まで持っていき、しあわせタマゴを使えばごっそり経験値が入る。孵化のために歩く時間がなくても電車に乗るだけでそれなりに距離は稼げる。
 また、大量にラッタができると思うが、ゲージ技であなをほるを覚えているのは取っておこう。じめん技が弱点であるほのお・でんき・いわ・はがねタイプに対抗できる。1匹では無理でも三匹もいればなんとかなる。
 同様に大量にできたピジョンは強い固体をビジョットに進化させよう。もしゲージ技にぼうふうを覚えている個体がいればナッシーを始めとするくさタイプやカイロスなどのむしタイプに有利に戦える。
 レアポケモンが居なくても戦いようはあるのだ。大量のキズぐすりを手に取りジムバトルを楽しもう!

みんな仲良くのsvnと俺様なgit

技術

 あるいは、リモートが主役のsvnとローカルが主役のgit。svnとgitの概念的な違いを述べるとしたらこんなところだろうか。
 お仕事でgitを使うようになったのだが、コマンドだけを見てもわけが分からず(それこそサルでも分かるを見てもである)モヤモヤしていたのだが、ようやく理解できたのでちょっと駄文を書いてみた。

 

 さて、svnは集中管理型のレポジトリであり、gitは分散管理型のレポジトリと分類分けされるらしい。
 svnはリモートにあるレポジトリからファイルをcheckoutして取得し、それをcommitしてリモートのレポジトリに変更分を反映させる。自分以外の変更分はupdateで取得する。
 これは、リモートにあるレポジトリの内容が正であるためである。レポジトリを唯一の原本として維持していくのがsvnだ。


 これに対してgitはローカルにあるレポジトリが正という形をとっている。作り方は簡単で、レポジトリ管理をしたいディレクトリでgit initを実行するだけだ。あとは、addしてcommitすれば終わりである。
 もちろん、これだけだとsvnで行っていたような共同開発はできない(ローカルのレポジトリをみんなが使うのであれば別だが)。そこで登場するのがpullとpushである。
 pullは他のレポジトリ*1と自分のレポジトリの差分を自分のレポジトリに反映させるコマンドで、pushは自分のレポジトリと他のレポジトリの差分を他のレポジトリに反映させるコマンドである。
 ようするに差分変更の方向を自分で指定できるのだ。指定する必要があると言い換えた方がいいか。この点がgitの素晴らしい点であり、また面倒くさい点でもある。


 gitでもリモートにあるレポジトリをローカルにコピーするコマンドcloneがあるが、git cloneはsvn checkoutではないのである。svnと決定的に異なるのはsvnはローカルもリモートも同一のものであるのに対し、gitはリモートとローカルは別物だということだ。git cloneはリモートのレポジトリを参考に自分用のレポジトリをローカルに作ることであり、作った時点もう別物といえる。
 なので、リモートのレポジトリに自分の変更分を反映させたければ明示的にpushしないといけないし、リモートが変更されたらそれを取り込むためにはpullしなければならない。commitやcheckoutの操作はあくまでも自分のレポジトリに対する操作なのである。


 逆に言えば、気にする必要があるのはローカルのレポジトリということである。ローカルのレポジトリに変更分を取り込みたいならば取り込むし、他のレポジトリに反映させたければ反映する。強制させずに自分のタイミングでやればいい。オレのレポジトリだ。自由に反映させるぜ! というのはgitなわけだ。これを素晴らしいと取るか、面倒くさいと取るかはそれこそ利用方法次第なのだろう。


 案件がきっちり決まっているいわゆるウォータフォール的に開発が進むところはsvnの方が管理しやすいし、案件が複数並列で動くようなアジャイル的な開発をするところではgitの方が管理しやすいとは思う。

*1:ちなみに他のレポジトリは自分のレポジトリとまったく無関係のものでも特に問題はない。あくまでもレポジトリ間の差分が反映されるだけなのだ。

プロマネのお仕事を考える

仕事

engineer.crowdworks.jp

 上記の記事を読んだ。なかなか面白い記事だった。もちろん自分もマネージャーやれと言われたら嫌がる方だ。だが、エンジニアとマネージャーの問題は40過ぎのおっさんエンジニアにとっては切実な問題でもある。そこで少々考えてみたい。

エンジニアのお仕事とマネージャーのお仕事

 ここでいうエンジニアとはプログラマーのことで、マネージャーとはプロジェクトマネージャーのことだ。
さて、両者の仕事を簡単に言うと

  • マネージャーのお仕事はコミュニケーションを通してエンジニアに効率よく仕事をさせること

ということになる。

仕事のやり方としては

  • エンジニアは効率よく仕事をするためにツールを作成したり持ってきたりして仕事をする
  • マネージャーはエンジニアに効率よく仕事をさせるための環境を作る

だったり、

  • エンジニアはフレームワークの組み合わせで適切なものを考え利用する
  • マネージャーはエンジニアの組み合わせでて適切なものを考えチームを作り上げる

だったりする。

 ふむ。こうして並べると相手がコンピュータか人間かなだけでやっていることはエンジニアもマネージャーも同じなのではないだろうか?

 

では、何が嫌なのか?


 コンピュータはエンジニアが要求した通りにしか動かない。振る舞いが意図したものと異なっているのは、要求方法が間違っているか、想定外の使い方をしているからだ。問題は明快だ。
 一方、エンジニア(というか人間)は要求した通りに動くとは限らない。プログラミング言語にあるようなAPIは基本的に非公開であるし、あるように見えても相手によって振る舞いはコロコロ変わる。同じ使い方でも相手の状況で振るまいが激しく変わったりする。しかも同じ条件下でも異なる場合がある。理不尽なものである。
 当たり前だが、コンピュータと人間では大きく違う。だが結局のところ何が嫌なのだろうか? 

 それは人間はデバッグできないからなのではないだろうか。

 コンピュータは何度同じ要求をしても嫌がらない。また相手を怒らせても(エラーなど)何度でも繰り返し実行できる。気兼ねなく実行できる。気遣いなど必要ない。
 一方、人間は相手を怒らせた後のリカバリ処理がとても大変である。何度も繰り返し試してエラーを起こす(怒らせる)ことなどできないし、地雷を踏んだら最後、二度と実行できないということもあり得る。
 エンジニアにとって相手を知るという行為はデバッグであるわけだから、それができない相手は嫌なのだ。

マネージャー向きなのは?


 逆説的にいうと、マネージャーに向いている人はこの人間デバッグ(要するに人間観察)が上手い人ということになる。デバッグ的行為を相手を不快にさせない程度に上手くやれる人がマネージャーに向いている。
 また、製品・サービスを作るのがエンジニアの醍醐味だが、それを作るためのチームを作り上げるのがマネージャーの醍醐味というのもある。製品を作るのはエンジニアしかできないかもしれないが、一方それを作るためのチームづくりというのはマネージャーしかできないものなのだ(兼任できるのは変態的な超人だけである)。
 モノを作成するためのモノを構築するのがマネージャー。ならばエンジニアと言えるのではないだろうか。
 もっとも、マネージャーに増員・環境手配などチーム作成のための裁量権が与えられていないと悲惨ではある。

 また、最悪なケースがある。それはプログラムを書いたことがないような人間がマネージャーをやることである。悲劇でしかない。理論を学んでいるとよりたちが悪い。そういう意味では、プログラマーの次のステージがマネージャーというのはある意味妥当だとも言える。お願いだからいきなりは辞めておくれ。
 個人的にはプロジェクトマネージャーの理論は勉強しておいて損はないというか、するべきではあると思っている。
 だが、一度もそれを体験する立場にならないまま適用するのはハッキリ言って百害あって一利なしだ。理論で目的を知り実践するというのが理想だとおもう。くれぐれも理論を学んだあとに実践させるとはならないように願いたい。

Web API The Good Partsの読書メモ(後編)

Web 技術

 Web API The Good Partsの読書メモの後編である。前編はこちら。

4.レスポンス形式

4.1 データの形式について

 リクエスト・レスポンスで渡すデータの形式は今はJSONがデフォルトである。ゆえにJSONを基本とし、必要に応じてXMLにも対応するという指針で行く。

 データのフォーマットの指定方法はいろいろあるが、クエリパラメータで指定するのが望ましい。例えばXMLで受け取りたい場合は、?format=XMLなどと指定する。

4.2  レスポンスデータの設計指針

 レスポンスデータの構造を考えるとき、以下のような方針で設計するとよい。

  • API のアクセス回数がなるべく減るようにする
    • 何度もアクセスが発生するような設計は避けること
  • レスポンスの内容をユーザが選べるようにする
    • 取得する項目を利用者が選択可能にする → fieldsクエリで指定するなど
    • いくつかのパターンを提供する
  • なるべくフラットな構造(平面、つまりは属性がすべて同じレベルにある)にする
  • オブジェクトに統一する
    • 配列もオブジェクトに包む → friends:[配列]の形
  • データの続きの有無(あるいは件数)をよく考える
    • データ全体を返す必要があるのか考える → 件数は情報として必要なのか?
    • hasNext:などでデータに続きがあることを表す
    • 次の要素への相対位置を渡してもよい
4.3  レスポンスデータの項目名称と形式
  • データの名前のポイント
    • 多くのAPI で利用されている同じ意味の一般的な単語を用いる
    • なるべく少ない単語数で表現する
    • 複数の単語を連結する場合、その連結方法はAPI 全体を通して統一する
    • 変な省略形は極力利用しない
    • 単数形/複数形に気をつける
  • 性別の形式
    • 生物学的:sex
    • それ以外:gender
    • 男、女以外ではないため、文字列形式が無難
  • 日付のフォーマット
    • 基本はRFC 3339
      • 2015-10-12T11:30:22+00:00 ※タイムゾーンUTCなので+00:00を使う
      • ただし、HTTPヘッダの形式はこれではないので注意
  • 大きな数の表現
    • 別の項目名で文字列としても表現する
    • 64ビットの数はJavascriptでは浮動小数点数として扱うため誤差が生じる
4.4 エラーの表現について
  • HTTPステータスコードでエラーを表現
    • クライアント側のエラーは400
    • サーバ側のエラーは500
    • エラーで200は返さないこと
  • エラーの詳細
    • レスポンスボディに入れる
  • エラーの際にHTMLが返ることを防ぐ
    • 500,503,404などWebサーバの設定がデフォルトになっている場合はHTMLが返る
  • メンテナンス時は503
    • 計画的なものならばRetry-Afterヘッダの値も返すのが適切
4.5 返却するHTTPステータスに迷ったら

 本とは無関係だが以下のサイトが参考になる。

 

postd.cc

 

5.APIのライフサイクル設計

 ここではAPIのバージョン管理のポイントを記す。

  • URIにバージョンを埋め込む
    • URIのパスの先頭に付ける v{数字}
    • メジャーバージョン番号のみで可
  • セマンティックバージョニング
    • {メジャー番号}.{マイナー番号}.{パッチ番号}
      • メジャー番号:後方互換性のない変更が行われた際に増える
      • マイナー番号:後方互換性のある機能変更、あるいは特定の機能が今後廃止されることが決まった場合に増える
      • パッチ番号:ソフトウェアのAPI に変更がないバグ修正などを行ったときに増える
  • バージョンアップの指針
    • 後方互換を保てないような修正をする時に上げる
    • パラメータの型変更などは廃止予定とし、代替のパラメータを用意することでいきなりなくさない
  • API提供の終了
    • 予め提供終了時の仕様を盛り込んでおく
      • 410 Goneを返すようにする
      • 410 が返った場合は公開が終了したことを意味する旨を明記
      • スマートフォンアプリ等は強制アップデートの仕組みを入れておく
      • サポート切れのAPIを使用している場合はバージョンアップを促す

 

6.セキュリティ指針

6.1 セキュリティを考慮した設計とは

 ポイントを以下に示す。

  • ブラウザがJSONJSON と必ず認識するようにする
    • メディアタイプはapplication/jsonにする
    • X-Content-Type-Options: nosniff も設定する → IEのコンテンツ自動解釈を無効にする
  • リクエストヘッダチェックを活用する
    • 通常のブラウザ操作では送信されないリクエストヘッダの有無をサーバ側でチェックする
    • ヘッダがある時のみ応答を返す
    • 例:X-Requested-With: XMLHttpRequest
  • JSONJavaScript として解釈不可能、あるいは実行時にデータを読み込めないようにす る
    • エスケープ処理を適切に行う
    • 配列ではなく、オブジェクトを返す
    • JSON無限ループという手もある
      • JSONデータの先頭に無限ループを仕込み、SCRIPT要素に読み込まれるのを回避する
      • JSONデータとして使う場合は、予め除去する

 ところでセキュリティの話自体は『体系的に学ぶ安全なWebアプリケーションの作り方』という良書があるため本格的にセキュリティを学ぶならばそちらを見た方がよい。ちなみに当ブログでも読書メモの記事を書いたことがあるので本に関心があるのであればチラ見して欲しい。

 セキュリティ カテゴリーの記事一覧 - Jの衝動書き日記

 

6.2  大量アクセス対策

 アクセスは無制限に許可するわけにもいかない。対処の指針を以下に示す。

  • ユーザ毎のアクセス数の制限
    • 単位時間あたりの最大アクセス数(レートリミット)を設ける
    • 検討事項
      • 何を使ってユーザーを識別するか
      • リミット値をいくつにするか
      • どういう単位でリミット値を設定するか
      • リミットのリセットをどういうタイミングで行うか
  • レートリミットオーバ時の応答
    • 429 Too Many Requests
      • エラー詳細を応答に含める
      • Retry-Afterでどれくらい待てばいいのかを指定することが可能 → 秒数、もしくは具体的な日付の指定
  • レートリミットの値を返す
    • HTTP のレスポンス
      • X-RateLimit-Limit : 単位時間あたりのアクセス上限
      • X-RateLimit-Remaining : アクセスできる残り回数
      • X-RateLimit-Reset : アクセス数がリセットされるタイミング
        • 値はリセットされるタイミングまでの秒数と、リセットされる時間を表すUnix タイムスタンプ(エポック秒
        • UnixタイムスタンプはHTTPヘッダの仕様定義上では本来NG → 時刻表示ならばOK

 参考文献

Web API: The Good Parts

Web API: The Good Parts