Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

1人開発のコードにオニオンアーキテクチャを導入した経験談

はじめに

この記事は Akatsuki Advent Calendar 2020 の 16 日目の記事です。

s-capybara です。普段は Elixir でモバイルゲームサーバーの機能開発をしていることが多いですが、時々 Go でツール開発も行っています。ほぼ1人で開発していてコードベースを自由に変更できるということもあり、このツールにオニオンアーキテクチャを導入してみました。色々あった困りごとが解消できたのですが、実際に導入することで新たに悩みが生まれたこともあり、その経験を共有できればと思います。

オニオンアーキテクチャとは

オニオンアーキテクチャ は Jeffrey Palermo 氏が提唱したアーキテクチャです。コードをいくつかのレイヤに分解して、依存性の逆転により DB やファイルにアクセスする処理を外に追い出し、アプリケーションの中心であるドメインロジックがこれらに依存しないようにするというものです。ドメインロジックのテストがやりやすくなったり、DB を他の機構に乗り換えやすくなったりといったメリットがあります。

依存性を逆転させてドメインを中心に置くというのが大事なポイントなので、ヘキサゴナルアーキテクチャやクリーンアーキテクチャと本質的には変わりません。個人的には、シンプルでレイヤのネーミングが分かりやすく他に比べて好みです。

作っているもの

今回オニオンアーキテクチャを導入した対象について少し説明します。

モバイルゲーム開発には大量のマスタデータを作成する仕組みが必要です。ゲームにおけるマスタデータというのは、例えば「A という名前のクエストがあって、その中には B という敵がいて、クリアすると C という報酬が貰える」など、ゲームの進行方法を指示するデータのことです。ゲームクライアントやゲームサーバーはこれを読み込んで、動的に処理を決定していきます。

今回取り上げるのはこのマスタデータ作成に使うツールで、Go で書いています。Excel にマスタデータの内容を記述し、事前に YAML で定義しておいたスキーマ(型情報)をもとに、JSON などゲームが利用しやすい形式のファイルに出力します。単に出力するだけでなく、データレコードをリリース日別に出力したり、海外版のために翻訳データを適用したりといった機能もサポートしています。このツールはデータを作成する人のローカル環境で実行するのですが、Go なのでバイナリの作成・配布が簡単で、あるゲームタイトルで使っている大量のデータでも1分ほどで実行が完了するぐらい高速です。

テーブルごとに以下のように YAML でスキーマを定義し、Excel の中身を解釈しています。

properties:
  id:
    description: "ID"
    type: "integer"
  name:
    description: "クエスト名"
    type: "string"
  stamina:
    description: "消費スタミナ"
    type: "integer"

f:id:s-capybara:20201216193234p:plain
サンプルの Excel

オニオンアーキテクチャの導入による改善

導入前に困っていたこと

最初はシンプルな実装だったのですが、開発を進める中で機能の数が増えていき、当初の想像以上に複雑なアプリケーションになっていきました。以下のように困りごとの数も増えていき、開発がやりにくくなってしまいました。

  • テストが入力データのファイル形式に依存している。
    • 複雑なロジックの途中に YAML を読む処理があるため、細かいテストのために逐一 YAML でテストデータを作る必要がある。
    • Excel で日時を表現するデータ形式が特殊なため、自分でテストデータを作ることが難しく、日時に関するテストをする際に Excel でテストデータを作る必要がある。
  • ファイル形式やライブラリの都合がコード全体に漏れ出ている。
    • YAML のデータ構造と実装に便利なデータ構造が微妙に異なるため、YAML にデータ構造を合わせるとそれを利用する側のコードが書きにくくなる。
    • YAML ライブラリの都合上、struct のフィールドを public にする必要がある。(そのため、関数でラップして上の問題を解決するということも難しい)
  • 新しい出力形式のサポートが難しい。
    • ツールを利用するゲームタイトルによって出力形式を変えられるようにしようと思うと、各形式を ON/OFF する分岐処理が煩雑になる。
    • 各形式で細かく設定変更できるようにするのも難しい。

改善後の構造

これらの問題を解決するために、オニオンアーキテクチャを導入しました。 以下のようにリポジトリのパッケージ(ディレクトリ)構成を変更し、依存関係が domain <- application <- infrastructure となるようにしました。

  • domain
    • Excel や YAML といった特定の形式に依存しない、ツールにとって最も重要なロジックを書く。
    • 外側のレイヤが使う interface を定義する。
  • application
    • domain のオブジェクトや、domain の interface を満たす infrastructure のオブジェクトを組み合わせて、ツールのユーザーが所望する単位の処理の流れを形成する。
    • コード量は非常に少ない。
  • infrastructure
    • Excel や YAML, JSON といった特定のファイル形式に依存したコードを書く。
    • domain で定義された interface を実装する。

内側で interface を定義することで依存性を逆転させ、ドメインロジックが特定のファイル形式に依存せずに済むようになりました。

オニオンアーキテクチャではドメインモデルとドメインサービスがはっきり分かれていますが、今回は、区別しつつも同じディレクトリに入れることにしました。ドメイン駆動開発(を今回きっちりやっているわけではないですが)において、これらを別の場所に置くと「ドメインモデル貧血症」を起こしやすくなると ヴァーン・ヴァーノン著『実践ドメイン駆動設計』 に書かれています。また、ドメインサービスが大きくなりすぎないように、かつドメインモデルがドメインサービスに依存しないようにしています(注意しなくても自然とそうなりました)。

改善されたこと

オニオンアーキテクチャのメリットそのものですが、以下のようにメンテナンス性が向上しました。

  • ドメインロジックがファイル形式に依存しなくなり、テストしやすくなった。
  • ファイル形式やライブラリに合わせるためのコードを一箇所に集約できるようになった。
  • 新しい種類のファイル形式に対応しやすくなった。

例として3つ目を少し詳しく紹介します。機能追加をしようとしていて、ツールを利用するゲームタイトルごとにファイルの出力形式を変更できるようにし、またそれぞれの形式について細かいオプションを付けたいという事情がありました。以前は決め打ちで特定の形式を使うようになっていたのですが、この箇所の柔軟性が上がり、設定ファイルを通じて任意の形式を選べるようになりました。また、同じ形式を異なるオプションで複数回利用するということもできるようになりました。

marshalers:
  # キーは出力先のディレクトリ名
  diff_check:
    type: json
    options:
      time_zone: JST
  server:
    type: message_pack
  client:
    type: message_pack
    options:
      hide_secret: true # ゲームクライアントが使わないテーブルやカラムを削除する

導入後に悩んだこと

各レイヤのテスト

どのレイヤにどの程度テストを書けば良いのか、というのは悩んだポイントでした。domain については、もともとの動機でもあるのでユニットテストを細かく書いています。一方で、application についてはとても薄いレイヤになり、分岐があるわけでもないので全くテストしないことにしました。代わりに、Excel や YAML でシンプルなテストデータを少しだけ作って、infrastructure を含めた E2E テストを書くことで application のテストを内包することにしました。手動での動作確認を頻繁に繰り返す手間を省くため、ツール全体が正しく動作することを保証したい事情もありました。

しかし、この対応だけでは不充分で、アップデートしたツールを利用者(マスタデータ作成者)に配布したところ、何度かトラブルが発生してしまいました。domain にバグが含まれるケースはなく、毎度 infrastructure に原因がありました。infrastructure が外部のファイルに依存していることを理由にあまりテストを書いていなかったのですが、冷静に考えてみると、いくつかの工程に分解できることに気づきました。

  1. テキストファイルを読み込む
  2. ライブラリを使ってパースする
  3. domain に合う形に変換する

2 の YAML のキー名を指定する場所で typo が発生しがちで、度々 0 や空文字列などのデフォルト値になってしまっていました。また、3の工程も意外に複雑でした。しかし、細かい機能を追加するたびに E2E テストを書いていては、テストの影響範囲が大きくメンテナンスコストが高すぎると感じました。

そこで、1 とそれ以外を別々の関数にし、2 の結果がライブラリ独自の複雑なデータ構造であり自分で作ることが難しいことも考慮して、テストコードの中にヒアドキュメントとして YAML などを書き、2 と 3 のみまとめてテストすることにしました。ライブラリや domain に強く依存しているので若干脆いテストにはなるのですが、特段メンテナンスしにくいということもなく、バグの発生を抑えられるようになりました。

巨大な domain の整理

オニオンアーキテクチャでは、ドメインモデルやドメインサービスの中身をどう構成するかは特に言及されていません。今回の実装では domain/schema, domain/record といったいくつかのパッケージに分割し、domain/schema <- domain/record などと横向きの依存関係を作りました。複数の domain パッケージから参照されるものもあれば、他の domain パッケージからは参照されないものもあります。今後は domain パッケージの階層を増やす可能性もあります。

domain パッケージの間には interface を挟まず、直接参照するようにしました。最初は interface を逐一定義しようと考えたのですが、ドメインモデルの全てのメソッドを interface 化しようとするとかなりの手間だったので、1人開発では割に合わないと思い、断念しました。

interface を定義しないとなると、スタブを使うことができず他のパッケージ (schema) を参照しているコード (record) のテストが難しくなります。そこで、厳密さよりもテストデータの作りやすさを優先して参照先のコード (schema) を変更しました。具体的には、schema オブジェクト生成時にオブジェクトが正当であることの保証をせず、少しずつ組み立てて、最後にバリデーションする形にしました。

// domain/schema/schema.go
package schema

func NewProperty(name string, propertyType PropertyType) *Property {
    // ...
    return property
}

func (schema *Schema) AddProperty(property *Property) *Schema {
    schema.properties = append(schema.properties, property)
    return schema
}

func (schema *Schema) Validate() error {
    // プロパティ名のバリデーションなど
}
// domain/record/record_test.go
package record

func TestRecord(t *testing.T) {
    sch := schema.NewSchema().
        AddProperty(schema.NewProperty("id", schema.Integer)).
        AddProperty(schema.NewProperty("name", schema.String).SetNullable(true))

    // ...
}

この例では、例えば schema.NewProperty の第1引数であるプロパティ名にはアルファベットや数値以外指定できないのですが、作る際は一旦気にせず、後でバリデーションしています。そのため、バリデーションを実行し忘れると不正なスキーマで処理が続行してしまう危険性があります。interface 定義の手間を減らしてテストをしやすくするためにバグのリスクを取っているということになり、これがベストな方法なのか、今でも迷いがあります。

もはやオニオンアーキテクチャの管轄外の問題ではありますが、ある意味、オニオンアーキテクチャの導入によって問題が明瞭になったと言えるのかもしれません。以前は他の問題が多すぎて、ドメインロジックとそのテストをどう整理していくかということに意識をあまり向けられていませんでした。

infrastructure 都合で domain 実装が変わることもある

「内側のレイヤは外側のレイヤの変更の影響を受けない」と思っていましたが、そうでない場面もありました。パフォーマンスチューニングをするにあたって、例えば DB を使う場面で、複数レコードをまとめて INSERT することで DB との通信回数を減らすなど、ドメインが DB の事情に全く無関心というわけにはいきません。「内側で interface を定義して外側で実装する」というのは「外側の変更の影響を受けない」とはイコールではありませんでした。

今回のツールでは、当初 Excel のシートを読む際に 360EntSecGroup-Skylar/excelizefunc (*File) GetRows という関数を使い文字列の二次元配列 [][]string として利用していましたが、大きいファイルを扱うにはパフォーマンス上不向きだったため、func (*File) Rows というストリーミングで読み込む方式の関数に乗り換えました。これに従ってドメインの interface も変更する必要がありました。

外側の事情で内側を変更することに抵抗を感じましたが、読み込み済みの [][]string データをストリーミング方式の interface で利用することもやろうと思えば可能ではあるので、「より汎用的な interface にブラッシュアップされた」と考えれば、悪いことではなさそうです。

まとめ

オニオンアーキテクチャを使うと、ドメインロジックが特定のファイル形式などに依存しないようになります。導入することで新たに悩むポイントもいくつか発生しましたが、テストがしづらいなどの様々な問題を改善できました。参考になる部分があれば幸いです。

Vim で外部から情報を受ける

この記事は Akatsuki Advent Calendar 2020 の 15 日目の記事です。

thinca です。普段は Vim を使って開発をしています。

Vim は外部ツールとの連携を得意としているソフトウェアで、外部コマンドの実行結果を取り込んだり、Language Server のような外部ツールを起動してデータをやり取りしたり、といったことが行えます。

一方で Vim は、Vim の外部から情報を通知してもらうのが苦手です。

本記事では、まず前半で、Vim が外部から情報を受け取る手段についてまとめます。後半ではこれらを用いて Vim でどのようなことが可能になるかについて検討します。

外部から情報を受ける手法

clientserver

Vim の clientserver 機能は、まさしく外部から情報を受けるための機能です。

clientserver 機能が有効になっている Vim を起動すると、その Vim は外部から情報を受けられるようになります。別の Vim から、別のサーバとして起動している Vim の一覧を取得したり、それぞれの Vim に式を評価させたりすることができます。

この機能には 2 つの問題があります。

  • クライアントも Vim である必要がある
    • これは CLI で vim をワンショットで起動させることで回避が可能です。
  • 環境によっては動作しない
    • 特に Linux 環境においては X サーバとの連携が必要になるため、X の入っていないサーバのようなシステムでは利用できません。

上記の理由から、利用はかなり限定的になります。残念ながら私は、普段はプライベートでは X の入っていないサーバマシンを開発機として使っているため、この機能は利用できません。

Job

Job 機能を使うことで、Vim は外部プロセスを起動してその標準出力を待ち受けることができるようになります。

この機能を使えば外部からの情報の受け取りが現実的に行えます。vim-lsp などはこの機能を使って Language Server から情報を受け取っています。

デメリットとしては、外部コマンドが必要になるため使用するコマンドによっては環境に依存してしまう点です。しかし、最近はシングルバイナリで動作するツールも比較的容易に作れるため、必要であれば専用コマンドを同梱してしまうという手もあります。

ch_listen()

現在はまだ利用できませんが、ch_listen() という関数の追加が提案されています。これがあれば、TCP ポートや UNIX ドメインソケットで入力を受け付けて、処理を実行することができるようになります。Job と違って外部ツールに依存しない点が魅力です。

現在はユースケースが不足していて、必要性についてあと一歩説得力が足りていないようです。もっと実用的なユースケースが提案できれば動きがあるかもしれません。

活用できそうな事例

上記の事情から、私は主に Job を使った方法を利用しています。ここでは、実際にどのような場面で活用できるか、あるいはできそうかを紹介します。

別の Vim でファイルを開く

これは clientserver 機能の主目的とも言えますが、例えばすでに Vim が起動していた際に別の場所で新たに Vim でファイルを開こうとした場合、Vim が複数起動してしまいます。

このとき、新しく Vim を起動する代わりにすでに起動している Vim でファイルを開く、といったことが可能になります。

ファイルの再読み込み

Vim は、開いているファイルが外部で変更されたかどうかは逐一チェックしていません。:checktime Ex コマンドを使うとチェックしてくれますが、必要な際に実行する必要があります。

GUI 版の Vim であれば、例えば Vim のウィンドウがフォーカスを得た際に実行される FocusGained イベントで実行すれば、ほとんどの場合をカバーできそうですが、CUI だと難しそうです。

こんなときに、外部から変更を通知したり、あるいは CUI の Vim がフォーカスを得たことを通知できれば自動でのファイル再読み込みが捗りそうです。

hubot-vimexec

拙作の、チャット上で Vim のコマンドの実行結果を見れるようにする bot です。

thinca/hubot-vimexec

よくある言語処理系の実行結果を出すものと違い、裏で Vim が常駐し、状態を保持しています。なので以下のようなことができます。

f:id:thinca:20201214203600p:plain
状態を保持した bot

これを実現するためには、実行中の Vim に対して外部からスクリプトを与える必要があります。

一番簡単な方法は、Vim 側で新しいスクリプトがないか逐一ポーリングすることです。実際、最初はこの方法で実装したのですが、さすがに 1 秒に何度も readdir() するのはディスクへの負担も気になってきます。

これを Job で置き換えたいのですが、この bot は環境を閉じ込めるために Vim の実行にネットワークを無効にした Docker を使っています。ポーリングの際は、バインドマウントを行ってそこに実行したいファイルを置き、終わったら結果のファイルを置いてもらっていました。これを、Job を使って Vim の内部でプロセスを実行し、外からこのプロセスに対して何らかのアプローチをする必要があります。

そこで、named pipe を使いました。これであればバインドマウントだけでやりとりができます。

まず、マウントした領域にリクエストを受け付けるための named pipe を作成し、Vim はこのファイルを cat コマンドで読み取る Job を生成して待ち受けます。

bot にリクエストが来たら、まずはそのリクエストに対するレスポンスを受け付けるその場限りの named pipe をマウントした領域に生成します。そしてスクリプトの内容と、レスポンスすべきファイル名をリクエスト用のファイルに書き込み、あとはレスポンス用のファイルを、Node.js の fs.readFile() で読み、読み込めるのを待ちます。

Vim 側はリクエストを受けると cat のプロセスがファイルの内容を出力して終了するので、スクリプトを実行し、結果をレスポンス用のファイルに writefile() で書き込みます。その後、次のリクエストのために再び cat の Job を実行します。

bot はレスポンスを受けとると、専用の named pipe を削除し、結果をチャットに伝えます。ちなみにレスポンス用に毎回別のファイルを用意するのは、複数のリクエストが同時に来た場合のためです。

この手法は Windows だと難しいため利用範囲は限られますが、必要なコマンドが catmkfifo だけであるため、Windows 以外であれば比較的容易に応用が効きそうです。

ch_listen() の模倣

ch_listen() はまだ使えませんが、要は、port を LISTEN し、接続があれば入力を標準出力に流し、標準入力から受け取った内容をクライアントに返すようなコマンドがあれば、Job を使って ch_listen() を再現できそうです。

そんな便利なコマンドがあればよいのですが…そんなスイスアーミーナイフのようなコマンドが…アーミー…?あっ!

というわけで netcat を使って ch_listen() のようなことをしてみましょう。

簡易ですが、Vim を起動して、以下のスクリプトを実行します(:source server.vim)。

function s:handle(req) abort
  if a:req.method ==? 'POST'
    return {
    \   'status': 200,
    \   'status_text': 'OK',
    \   'body': execute(a:req.body),
    \ }
  endif
  return {
  \   'status': 404,
  \   'status_text': 'Not Found',
  \   'body': json_encode(a:req),
  \ }
endfunction

function s:parse_request(msg) abort
  let req = {}
  let matched = matchlist(a:msg, '^\v(.{-})\r\n\r\n(.*)')[1 : 2]
  let [header_block, body] = matched[1 : 2]
  let start_line = split(header_block, "\r\n")[0]
  let req.method = split(start_line, '\s\+')[0]
  let req.body = body
  return req
endfunction

function s:out_cb(cxt, ch, msg) abort
  try
    let req = s:parse_request(a:msg)
    let res = s:handle(req)
  catch
    let res = {
    \   'status': 400,
    \   'status_text': 'Bad Request',
    \   'body': v:exception,
    \ }
  endtry

  let response = join([
  \   printf('HTTP/1.1 %d %s', res.status, res.status_text),
  \   'Content-Length: ' .. len(res.body),
  \   '',
  \   res.body,
  \ ], "\r\n")
  call ch_sendraw(a:cxt.in, response)
endfunction

function s:start(port) abort
  let cxt = {}
  let job = job_start(['nc', '-lkp', a:port], {
  \   'in_mode': 'raw',
  \   'out_mode': 'raw',
  \   'out_cb': funcref('s:out_cb', [cxt]),
  \ })
  let cxt.in = job_getchannel(job)
endfunction

call s:start(11111)

サンプルなので処理をかなり省略していますが、どこからどう見ても HTTP サーバです。このサーバは 11111 番ポートで待ち受け、テキストを POST メソッドで投げると Vim script として実行し、結果の出力を返します。

というわけで、curl でアクセスしてみましょう。

$ curl -d 'echo "Hello, Vim server!"' 'localhost:11111'

Hello, Vim server!

結果が返ってきました!やりましたね。

注意点として、netcat には様々な派生バージョンがあるため、実際に使えるかどうかは環境に入っている netcat の機能について知る必要があります。

まとめ

Vim で外部から情報を受け取る手法についてまとめました。また、それらの活用方法についても検討しました。

これらの応用により、Vim をもっと便利にできる可能性があるのではないかと考えています。この記事がその一助になれば幸いです。

ボトルネックマネージャーを抜け出す方法

こんにちは、ゆのん(id:yunon_phys)です。この記事は Akatsuki Advent Calendar 2020 の12日目の記事です。

アカツキは10周年を迎え、それと同時に経営体制が一新され、経営チームExecutive Leadership Team(ELT)が結成されました。 経営戦略の基本方針も変更となり、社内の構造的にも社内の方針としても、アカツキにとって大きな変化のある1年となりました。

私もELTのメンバーとして現在主力であるゲーム事業の職能組織を束ねる立場(Chief of Staff, Games / CoSG)となり、影響の範囲も、責任の範囲も大きく拡大しました。 これまでは、私はVP of Engineering(VPoE)としてエンジニア組織を束ねる立場にあり、一つの職能のことだけを考えておけば良かったです。 しかし今では、エンジニアだけでなく、デザイナーや企画職・QA・マーケターなど、様々な職能を理解し、文字通り全ての人が輝けるように組織を作る必要が出てきました。 昨年の私の個人的なブログで、エンジニア組織だけでなく全体的に良くしたいという記事を書きましたが、1年後にまさかそれが出来る立場になるなんて、未来のことはわからないものですね。

この新しい役割になって、どんな組織を作っていくのか、ご興味のある方は以下のインタビュー記事を読んでみてください。 voice.aktsk.jp

・・・さて、前置きが長くなりました。 CoSGになったという話をしてきましたが、実はVPoEを今も兼任しています。 兼任と言うと聞こえは良いですが、同時に業務をやれるわけではないので、どうしても責任範囲の大きいCoSGの業務が優先されてしまいがちです。 しかし、VPoEとしての業務が無くなるわけではないので、自分がボトルネックになってしまっているのが次第に浮き彫りになってきました。

今回の記事は、そんなボトルネックになってしまったマネージャー業をどうやって抜け出そうとしているのか*1、この1年の取り組みを書いていきます。

Engineering Manager(EM)が同じ情報を持てるようにする

以前はプロジェクトに直接入ったり、1on1する頻度も高く設定していたので、様々なエンジニアに関する情報を自分に集約して様々な意思決定できていました。 しかし、CoSG業が多くなり、エンジニア以外の情報が増える一方で、エンジニアの情報が減っていってしまいました。 特に困ったのが異動・配属で、発生する度に情報をかき集めて、そこに所属しているEMや他のリーダーと交渉して、といったことに追われるようになってしまいました。

この状態を脱却しようと、一度立ち止まって何が問題なのか考えたところ、私に情報を集めようとしている行動そのものが良くないことだと気が付きました。 例えば、異動・配属については、プロジェクトの短期的な課題解決として実行する場合もあれば、中長期的な会社戦略を見据えて実行する場合もあります。 これらを広い視点で実行するためには情報を正しく持っていなければいけない、と思っていました。 しかし、そもそも情報を正しく持っているのが自分だけの状態になっているのが問題でした。

私の信念としては、「同じ情報を持っていれば、全員が同じ意思決定にたどり着く」、です。 裏を返すと、全員が同じ意思決定にたどり着くためには、同じ情報を持っていなければならないということです。

そこで、私に集まってくるエンジニアに関する情報や会社の方針情報などを、可能な限りEMに共有するようにしました。 具体的には、月1回全EMが集まる場を設け、そこで共有しながら、EMの意見を出し合うようにしました。 また、全EMにはそれぞれの抱えているプロジェクトの情報や課題などを共有してもらうようにしました。 全EMが同じ情報を持っている状態を一応は出来たことになります。

これでうまくいくと思ったのですが、残念ながら、私の満足いく結果にはなりませんでした。 それはEMに与えられている権限・責任が適切でなかったからです。 権限・責任が適切でなかったために、EMが自発的にやれる領域を狭めてしまっていました。 そこで、権限・責任を適切に委譲する作業に取り掛かりました。

適切な権限委譲をする

私は適切な権限委譲こそ組織を次の段階に成長させるために、重要な要素だと考えています。 オレンジ組織からグリーン組織のブレイクスルーの一つは権限委譲である、と以下の書籍でも書かれています。

過去には私とCTOとの間でManagement3.0のプラクティスの一つであるDelegation Boardを作りました。*2

hackerslab.aktsk.jp

今回も同じ方法で私と全EMが集まり、Delegation Boardを1日がかりで作成しました。 私はDelegation Boardの結果も大事だと感じてますが、それ以上にそれを決めていく過程こそ意味があると思っています。 実際にこれを作っていく過程でDelegation Levelが一致せず、お互いの想定している権限やそれに伴う責任を丁寧に言語化していきながら、時間をかけて一つ一つ対話していきました。 最終的に出来たDelegation Boardは全員が満足いく結果になりました。

Delegation Boardを作ってから、明らかにEMの動きが変わりました。 何を自分たちがやるべきなのか、どんな責任を持っているかが明確になることが、ここまで強力なのかと改めて理解できました。

EMのサポートを手厚くする

ここまででは、私の負担が下がり、EMへの負担が上がる一方です。 ある意味望んだ結果ではあるのですが、それはあまりに不公平な感じがします。 もし私が逆にEMの立場だったら、今までやったことの無い仕事が増えてしまうので、戸惑ってしまうだろうなと思いました。

そこで、EMの業務をよりサポート出来るように、 @noto さんに各EMの1on1を依頼しました。 @noto さんは私とは違う観点で人と向き合ってくださる方で、とても信頼のおいている方です。 EMの悩みに適切な距離感でアドバイスしたり、聞き役に徹してくださっているようで、早い段階でEMの悩みに解決に向けて動けていってると感じてます。

@noto さんのように第三者的なEMのサポートは、僕のように全てをケアしきれないときに、とても心強い存在です。

何が変わったか

これらの活動により変わったことは、主には以下の通りです。

  • 全エンジニアの状況の見える化シートが自発的に作られ、EM全員でメンテするようになった
  • エンジニアの異動・配置をほぼ私なしで進められるようになった
  • ダイレクト・リクルーティングを私なしで進められるようになった

これまで、自分が記録ではなく記憶にいかに頼りすぎていたのだろう、と感じさせられてしまいました。

これらの業務が自分の手元から離れていくことで、より事業の戦略・戦術の策定に落とし込む時間が増えてきました。 まだまだ自分がボトルネックになっていることはたくさんあるとは思いますが、少しずつでも解消していけるよう工夫していきます。

*1:兼任は続いているので、ボトルネックになっている事実は払拭できていない

*2:Delegation Boardは強力で、これを作る過程とそれによる情報の透明さからすっかり虜になってしまいました。それ以来、社内で何かと権限委譲について語る場面が増えて、Delegation Board作成のファシリテーションを何度もしたことがあります。

新メンバー受け入れ時に各セクションの夕会に参加して回るツアーを実施している話

この記事は Akatsuki Advent Calendar 2020 9日目の記事です。

はじめに

アカツキでサーバサイドエンジニアをしている jyll です。

50人を超える大規模チームのオンボーディングとして、チーム内夕会ツアーを実施してみたら新規メンバーにも既存メンバーにも好評だったので、その際の知見を共有してみます。

チームの構造とリモートワーク下で感じていた課題

私の所属しているプロジェクトは全体で50人以上の大規模チームで、企画職・サーバサイドエンジニア・クライアントエンジニアなどのセクションにおおよそ10人ずつ分かれています。現在はほぼフルリモートワークになっています。

リモート開始前からのメンバーとその後から入ってきたメンバーで情報格差が発生していて、 リモートワーク導入以前から見知っていたメンバーはコミュニケーションに不便をほとんど感じないが、そのあとに入ってきたメンバーは

  • 誰に何を相談したらいいのかわからない
  • チーム全体としてどういった流れで開発が進んでいるのかわからない

などといった不安を抱えてしまうという課題が挙がっていました。

夕会ツアー

そこで、新規メンバーのチーム全体での自己紹介は名前を紹介する程度にしておき、その後各セクションの朝会や夕会に一度ずつ参加して回るツアーを実施してみました。

各セクションの業務になるべく業務に負荷をかけない形で、チーム全体としてはどんな人たちがいて、普段どういう仕事をしているか、誰とコミュニケーションを取っているのかに触れる機会を作ることを目的としています。

各セクションで毎日集まっているミーティングの日程を洗い出して、それぞれ1日ずつ都合のいい日をピックアップしてもらって引率者と新メンバーをそこに混ぜてもらいます。 そこで普段通りの報告を行ってもらったあと、新メンバーとの相互の自己紹介タイムを10分ほど取ってもらう、という流れです。

f:id:jyll:20201209110656p:plain
こんな感じで呼びかけて、その日からさっそく色々なところにお邪魔して回りました。

実際やってみてどうだったか

全体自己紹介後にすぐ担当セクションに引き渡してしまうよりも、丁寧なオンボーディングになりました。 チームとして感じたメリットですと、以下2点が大きかった印象です。

  • 役割の紹介という形ではなく実際に回している業務の話が聞けるので、行なっている業務のイメージがつきやすい
  • 少人数かつ普段一緒に業務をしている人たち中心での自己紹介になるので、より具体的な趣味の話が出てきやすい

おわりに

この試みは実はアカツキの他のプロジェクトで先に導入されていて、そこでうまくやれているとの報告を受けて導入してきたのですが、期待通りの結果が得られました。

大規模なプロジェクトのつながりを作るきっかけとして、簡単に導入できるオンボーディング施策かと思います。おすすめです。

【ハチナイ】完全リモートでクライアントエンジニアインターン【15日間】

こんにちは。イオジンと申します。7月20日〜8月12日まで、15日間インターンをしました。

日本に来て、半年引きこもり生活をしていて、アカツキに内定をもらってインターンを始めました!外国人の中でアカツキにインターンをしてみたいとかアカツキに興味がある人に、有益な文章になってほしいです。

 

目次

  • 自己紹介

  • インターンに参加した経緯

  • 実装

    • システムボイスを鳴らす機能を見直す

    • スキットの話者をProfileID指定にする

    • スペシャルログボのオブジェクトを見直す

    • SDキャラの配置アルゴリズムを見直す

  • 苦労した事

  • 提言

    • Design Pattern

    • C# Convention

    • 本当の日本語

    • チームプロジェクト

  • さいごに

 

 

自己紹介

韓国の東国大学でComputerVisionとか映像処理を勉強しました。

数学とかプログラミングも好きて、学生の時はRayTracing Renderingを作ったりVoxel Renderingを作ったりしました。

f:id:LeeEohJin:20200813114805g:plain

Ray Tracing Rendering

f:id:LeeEohJin:20200813114912p:plain

Voxel Rendering

私は小学校からずっとツクルでゲームを作りました。そしてゲームはストーリーが大事な日本のゲームをモチーフにしているので、将来日本でゲーム開発をしたいと思っていました。

趣味はゲームと昔のJPOPを聞いたり、Netflixでアニメを見たりします。結構、レトロな事が好きなのでゲームとかアニメとか映画も昔のことが好きな変人です。

 

 

インターンに参加した経緯

まずは、私は韓国で大学を卒業して日本に来ました。去年の12月に来て就活を始めました。韓国ではほぼ卒業した後、新卒として就活するのが普通だったので、日本に来てびっくりしました。

そして、コロナが出て私は3月からはずっと自粛生活、でも、就活をずっと続けました。そして、アカツキで内定をもらいました。でも、その時は他の会社でバイトしていたのでそれが終わってアカツキのインターンを始めました。私はハチナイのチームで働くことになりました。

 

 

実装

 

インターンを始めて私がもらった課題は機能を改修する仕事でした。

システムボイスを鳴らす機能を見直す

f:id:LeeEohJin:20200817125240p:plain

鳴らす対象の選手番号が飛び番になった場合に対応できていなかったのが問題でした。

簡単に鳴らす対象を指定できるように、Scriptable Objectを利用して改修しました。

スキットの話者を選手番号指定にする 

f:id:ytfkbc:20201023151233p:plain

スキットではセリフを言う選手は選手の名前に依存していて、選手が同姓同名の場合問題が発生していました。なので、選手の名前の代わりに選手番号を使うようにしました。

そして、変換を誰でも簡単にできるようにするため、UnityのEditor拡張を行いました。

スペシャルログボのオブジェクトを見直す 

f:id:ytfkbc:20201023151859p:plain

(画面は開発中のものであり、実際の仕様とは異なる場合があります)

スペシャルログボのアイテムアイコンが称号、トロフィー、TPを表示出来ない問題がありました。

f:id:ytfkbc:20201023151950p:plain

(画面は開発中のものであり、実際の仕様とは異なる場合があります)

ノーマルログボでは、その問題がなかったので同じオブジェクトにしました。

SDキャラの配置アルゴリズムを見直す

そして、私がハチナイで気になった部分がありました。SDキャラの配置の部分がずっと気になってその部分のロジックを見直したいと言いました。結構細かいところだったが試しに実装をしてみようということになりました。

f:id:ytfkbc:20201023152044p:plain

キャラがランダムに配置されたので、配置がよくない場合がありました。その場合はキャラをクリックするのも不便だし、見た目にも良くないと思って色々アルゴリズムを考えてみました。

f:id:ytfkbc:20201023152111p:plain

(画面は開発中のものであり、実際の仕様とは異なる場合があります)

ガウシアン分布を用いた確率的配置がいいかなと最初は考えましたが、確率に頼るのは完璧な解決方法ではないと思っていました。 そこで毎回配置を確認して最適な位置を探すアルゴリズムを設計して実装しました。

固定の配置場所にしか対応しなかったので採用されませんでしが、問題提起から解決までを自分でやった特別な経験でした。

 

 

苦労した事

外国人として、日本で働き始めた人はみんな同じと思います。時々日本語が聞こえない時があります。リモートではそれはもっと大変になります。

f:id:LeeEohJin:20200813114912p:plain

日本語聞き取れない

Nativeなら、一つの単語とかは聞こえなくても文章を理解する事には何も問題がないですね。でも、私みたいにまだ日本語がNativeレベルじゃない人にとっては地獄です。リモートの環境では時々ネットの問題とかスピーカーやマイクの問題でよく聞こえない場合があります。結構かなりあります。なんかの単語が聞こえなかったらパニックになる時がありました。その時はまた質問するしかなかったです。その時、やっぱり相手には失礼なので、とても申し訳ないと思いました。

現場に初めて飛び込んだ初心者として、苦労した事もあります。学生の時、GitHubを学べる授業もあんまりないし、学生はGitHubを自分のポートフォリオ·サイトとして使えるんじゃないですか?(私はそうでした。笑)

だから簡単な使い方とか簡単な機能しか分からないまま卒業する人が多いと思います。私もそうして、インターンを初めて色々困る時がありました。

  

提言

全般的に見たら、就活で私はかなり珍しいケースと思いますが、今後アカツキにEntryすることになる外国人にとって、私の経験が誰かの手伝いになったらいいなと思う気持ちで伝えたいことがあります。

 

Design Pattern

学校でよく勉強したDesign Patternとかが力になったと思います。MVC Patternで作られたと思って分析すれば、コードレベルではあんまり難しいことは無いと思います。そして、Abstract Factory Pattern(これは明確ではなかった気がします)とかBuilder Patternを使ったコードも見えました。

多分、みなさんが知っていると思いますが、少なくでも、MVC Patternを勉強するためにはStrategy PatternとObserver PatternとComposite Patternの勉強が必要ですね。個人的おすすめ本、二つです。

一番有名なGOF Design Patternsです。

個人的にはこちの方がもっとおすすめです。分かりやすいし、Javaを基にDesignの説明があったのでC#で実装するUnityのエンジニアはコッチがもっと楽と思います。そしてこれを先に見てGOFを見たら改めて見える部分もたくさんありました。 

 

C# Convention

「ハチナイ」 ではC#のStandard ConventionでCodeが出来上がっていますが、前の会社で働く時は、かなり特別なConventionを使って、そのCoventionに慣れて、変な規則のコードをよく書きました。それがちょっと恥ずかしかったので、皆さんはC# Standard Conventionに慣れてください。

 

本当の日本語

本当の日本語?それは何?なら今まで私が学んだ日本語は偽物??と思う人がよくいると思います。本と本当の日本語はちょっと違うと思いました。本ではあんまり説明されてない単語とか文法がたくさんあります。そして、省略不完全な発音です。

その壁を越える方法は色々あると思いますが、私のおすすめは日本の番組です。

番組が一番いいところは単語を省略したり発音をはっきり言わない時がよくあるからです。その環境が一番実戦に近い環境と思います。私もまだまだ足りないですけど、一緒に頑張ってみましょう!

 

チームプロジェクト

一人でゲームを作る事とチームでゲームを作るのは全然違います。一番いいのはチームで作業しながら個人のプロジェクトを続けることがBestです。自分の後を考えて自分が学びたいのを個人プロジェクトにして、チームプロジェクトでは協業ツールの使い方やコミュニケーションの方法と協働してコードを組む方法を勉強すればいいと思います。学校以外で機会は絶対色々あります。

そして、出来ればGitHubをTerminalで作業してみてください。

私もSourceTreeに慣れて、またTerminalに慣れるように勉強しています。SourceTreeでは出来ないし、ちょっと使い辛いのがTerminalで簡単にできる場合が多いと思いました。

大きいプロジェクトを触ると他の人のコードを見る機会とかが良くあるからそれを見るだけで、自分のコード力が上がると思います。本当に難しい課題は、会社が私たちに任せてくれません。難易度よりコードの綺麗さが色々問題になると思います。(私も問題です。。悲)

 

さいごに

私も色々足りないですが皆さんに色々これしてこれして言って恥ずかしいです。これを読む皆さんは私よりもっと実力あるエンジニアと思いますが、でも、なるべく皆さんの成長に私のインターン経験が役に立ったらいいなと思って書きました。

自分の足りないところが見えて、成長の為の良い基盤になったインターンでした。そして優しい皆さんが手伝ってくれるので、不安なくフルリモート環境でインターンをすることができました。ハチナイのインターン楽しかったです❗️ 

読んでくれてありがとうございます。