Classi開発者ブログ

教育プラットフォーム「Classi」を開発・運営するClassi株式会社の開発者ブログです。

GitHubのテンプレートリポジトリを継続的に運用する

フロントエンドエキスパートチームのlacolacoです。今回はClassiの中で運用しているGitHubのテンプレートリポジトリについて、その運用方法を紹介します。これが最善である自信はないですが、一例として誰かの参考になれば幸いです!

テンプレートリポジトリ機能とは?

docs.github.com

テンプレートリポジトリは通常のリポジトリと同様にcloneやpull、pushなどできるGitリポジトリとして機能します。テンプレートリポジトリが通常のリポジトリと違うのは、新しいリポジトリを「テンプレートから作成」できる点です。 テンプレートからリポジトリを作成することは、リポジトリをフォークすることに似ていますが、以下の点で異なります。(ドキュメントより引用)

  • 新しいフォークは、親リポジトリのコミット履歴すべてを含んでいますが、テンプレートから作成されたリポジトリには、最初は 1 つのコミットしかありません。
  • フォークへのコミットはコントリビューショングラフに表示されませんが、テンプレートから作成されたリポジトリへのコミットはコントリビューショングラフに表示されます。
  • フォークは、既存のプロジェクトにコードをコントリビュートするための一時的な方法となります。テンプレートからリポジトリを作成することは、新しいプロジェクトを素早く始める方法です。

Classiにおけるテンプレートリポジトリ

テンプレートリポジトリは似たプロジェクトを何度も作成するときの雛形を管理する方法としてとても適しています。その一例として、Classiのフロントエンドエキスパートチームでは、AngularJSからAngularへの段階的移行を支援するテンプレートリポジトリを作成しています。

Classiのコードベースは機能やデプロイ単位に基づいていくつかのリポジトリに分割されています。そのため、ひとつの大きな移行ではなく、いくつもの段階的移行が並行して進行することになります。

AngularJSからAngularへの段階的移行では、AngularコンポーネントをCustom Elementsに変換して、それをAngularJSを利用している旧実装のHTML中に埋め込んで置き換えていくという手法を取っています。Angularコンポーネントは単にCustom Elementsにするためだけのソースコードではなく、移行の最終段階ではそのままAngularアプリケーションとして根本から置き換え可能である状態を作っていきます。

つまり、ひとつのプロジェクトの中でAngularのソースコードを通常のアプリケーションとしてデプロイできるビルド設定と、Custom Elementsのパッケージとしてデプロイできるビルド設定の共存が必要になります。毎回それぞれのプロジェクトにリポジトリの作り方をレクチャーしていくのは骨が折れますし、設定を改善したときにそれを全体に伝えるのも共通の土台がないため難しいです。

テンプレートリポジトリを運用することで、どのプロジェクトも同じスタートラインになっており、設定の改善も同じ変更を適用できることをある程度保証できるようになりました。また、実際にプロジェクトを進めていく中での発見をテンプレート側へ還元するループも生まれやすくなりました。

継続的な運用のための変更履歴

このようなテンプレート的存在は時間の経過につれメンテナンスがされなくなったり、テンプレートから新しく作成した時点からいままでの差分がわからないということになりがちです。この問題を緩和するために、テンプレートリポジトリの変更履歴を文書化しています。

具体的には、テンプレートリポジトリ内ではConventional Commits のルールに従うことで、 standard-version CLIを使ってCHANGELOG.md を自動生成できるようにしています。キリのいいタイミングでテンプレートのバージョンを更新することで、「テンプレートを更新したので各チームで変更点の追従お願いします」というコミュニケーションが容易になります。 コミットログだけではノイズが多いため、Conventional Commitsによってfeatとfixの差分がまとめられることで本当に重要な変更だけを伝えやすくなっています。

f:id:lacolaco:20210218102146p:plain
CHANGELOGの例

今後の課題

現状どうしても人力に頼っているのは、テンプレートのバージョンアップを派生先のリポジトリ達に伝えに行くところです。ここは何かしらの方法を使って自動化できないかと考えているところです。たとえばGitHub APIでそのテンプレートから作られたリポジトリを走査してIssueを作成にしにいくなどできるかもしれません。 また、変更の追従自体もテンプレート側のDiffをなぞって真似することになるため、マイグレーションパッチを適用するだけで済むようになれば楽になるかもしれないと考えていますが、テンプレートからの作成後に加えられた変更でパッチの互換性がなくなってしまうケースも考えると一筋縄ではいかないですね。

とはいえ、変更履歴とバージョニングを自動化するだけでもテンプレートリポジトリの運用がやりやすくなりました。読者の中でテンプレートリポジトリを運用している方の参考になれば幸いです。

SQL勉強会を通して痛感したデータ民主化への長い道のり

こんにちは、ClassiデータAI部の石井です。 私は2019年4月にソフトバンクからClassiに出向し、マーケティング部を経て、現在データAI部でデータエンジニアとして分析基盤の構築を担当しています。今回は私が現部署で最初に担当したSQL勉強会についてご紹介します。

背景

2020年春頃から、新型コロナウイルスの影響による休校や教育現場の急激な状況変化に対応するため、Classiサービスの詳細な利用状況把握の必要性が高まっています。 Classiは弊社の強みともいえる膨大な教育データを蓄積していますが、残念なことに全社的には貴重な教育データを活用しきれていないことが課題でした。 2020年夏に全社で行った「データAI部に期待すること」に関するアンケートでも、「基礎的なデータ活用方法を教えてほしい」という回答が多く寄せられました。 この状況をふまえ、データ活用のための知識の底上げを行い、全社的なデータ活用へコミットするべく、オンラインでのSQL勉強会を開催することにしました。

Y:やったこと

ターゲティングと目標設定

顧客のニーズを正しく捉えたプロダクトを継続して提供するためには、カスタマーサクセスとディレクターがデータ分析するスキルを身につけることが必要です。しかし、Classiではデータ活用の程度にばらつきがあり、施策の打ち出しや機能リリースを行っても、データを用いた効果検証ができておらず、その結果、さらなる施策の打ち出し等の際に顧客にデータに基づいた提案ができないという状況でした。 この状況に鑑み、最もデータを活用してもらいたいこの2職種を中心に参加者を募集。参加希望のあったカスタマーサクセス5名、ディレクター9名、セールス1名に対し勉強会を実施しました。 もともとは全社で1週間あたりの分析基盤利用者数が20名に満たない状況でした。まずは利用者数を20名以上とすることを目標に設定しました。

コンテンツ

業務におけるデータ活用のイメージがわきやすいように、カスタマーサクセスとディレクターそれぞれに各業務に近いデータを準備しました。 また、講義は参加者の習熟度を測るため下表のようなレベルに分けて行いました。

Lv. 説明 主な文法
0 SQLとは/RDBとは/Classiのデータについて
SQLの処理順序/データ型
1 1つのテーブルを利用して、データ抽出ができる select/from/where/and/or/in/case/like
2 1つのテーブルを利用して、集計ができる order by/group by/having
count/sum/avg/distinct/
3 1つのテーブルを利用して、高度な集計ができる with/日付関数/日時関数
4 2つ以上のテーブルを利用して、抽出・集計できる join(left,inner,cross,full)/unionall

データAI部3名が講師を担当し、以下の流れで学習を進めました。

講義編:SQL基礎を学んでもらう(1〜5週目、1時間/週を計5回)
実践編:チームに分かれて実際に分析をしてもらう(6〜7週目の計2週間)
分析結果発表会(1時間)

工夫したこと

講義編

オンラインでの勉強会は、参加者のリアクションや講義のペースが速すぎないかが分かりにくいため、講義の最後に演習問題を入れ理解度をチェックする等、可能な限り参加者全員が理解を深められるようにしました。

実践編

2チーム(3名ずつ)に分かれて実施し、各チームに講師が1名ずつサポートで入りました。分析を行う上では、以下のステップを踏みました。

  • 各チームメンバーが分析してみたいテーマを出し合う
  • 出てきたテーマに対して講師が分析の難易度を設定する
  • 難易度をふまえてチームメンバー自身で分析テーマを選んでもらう
  • 抽出するデータの順序を決めて段階的に抽出してもらう
  • 一つ一つ抽出できているかを講師が繰り返し確認、解説する

なお、ディレクターは全員他業務との調整がつかなかったためディレクター向けの実践編を開催できず、課題を残す結果になりました。

分析結果発表会

各チーム15分で発表・質疑を行いました。その後、今後のデータ活用の参考として、普段からSQLを使ってデータ分析しているカスタマーサクセスのメンバー2名に、実際に業務でどのようにデータ分析しているかについて15分ずつ説明してもらいました。

W:わかったこと

成果

勉強会開催中は講義内で分析基盤を使用したため、目標利用者数20名以上を達成することができました。 勉強会終了後は再び利用者数が減ってしまったものの、勉強会実施前に比べると利用者数が増え、勉強会の成果が出た形となりました。

f:id:yaya-data:20210127184727p:plain
分析基盤利用者の推移

振り返り

勉強会後、講師陣で振り返り、以下の気づきを得ました。

  • 講義を進める中で、Lv.2のgroup byや集計関数の部分は概念が難しく特につまづきやすいので、演習問題の経験を増やして理解を深めるのが最適だと思いました。

  • 実践編で、チームに分かれて分析した方がチーム内で意見を出し合って良い分析ができると予想していましたが、実際は各チーム内にいる講義編の習熟度が高いメンバー1名に依存する状況になり、全員の分析力を高めることの難しさを感じました。

  • SQL初心者から「with句の抽出名やカラム名に日本語は使えないのか」という質問が出ました。日本語を使用した方が各データが何を指すかわかりやすいという意図かと思いますが、私には日本語にしようという発想がなかったので新鮮なコメントでした。

  • 今回、本人の強い希望で勉強会に参加していたセールス担当から、「セールスはデータ抽出しないがデータを見ることはよくあるので、データ抽出を依頼する際にどの程度作業時間を要するか、どのように依頼したらスムーズかが分かり参考になった」と言ってくれました。セールスは業務中にデータ抽出する時間がないため今回はターゲット外としていたのですが、データ抽出との関連が薄そうな職種でも、勉強会への参加でデータを扱う業務への理解が深まるという思いがけない効果がありました。

  • 分析発表会の参加者の気づきの中も参考になるものがあったので以下ご紹介します。

    • アウトプットイメージを先に明確にしたうえでテーブル抽出する
    • 仮説を立てる際の目的を明確にすること
    • 抽出データの構成を考えるスキルの向上(横持ち?縦持ち?等)

フィードバック

勉強会後、以下の3つの観点で参加者にアンケートを実施しました。

  • 習熟度
  • 満足度
  • 今後のデータ分析に対する主体性

勉強会参加者16名のうち半数の8名が習熟度最高レベルのLv.4までマスターすることができました。また、講義編、実践編、講師の説明、コンテンツに対して5段階評価でそれぞれ4.6以上と、高い満足度を得られました。 しかし、勉強会本来の目的である「今後のデータ分析への主体性」に変化があったかのアンケートの結果、全体では5段階評価で平均3.2、SQLを習熟度Lv.4までマスターした参加者でも評価は平均3.7となり、高い満足度の割に低いスコアとなりました。 理由として、参加者コメントに「分析する時間が取れない」、「SQLの理解はできたが、現業務にどのように活用したらよいかが分からない」といったSQLの知識の有無とは別のところに課題があることが分かりました。

参加者コメント(一部)

  • 意欲はあるのですが、他業務との調整がなかなかつかず。。
  • ここで得た知見を今後どう活用していけるかがまだ見えてはいない
  • 現時点で具体的なテーマ設定ができない
  • より身近に使える題材を演習にできるとなお良かった

T:次にやること

今回の勉強会を通じて、データ活用を進めるためにSQLを学習することと別に、(1)分析時間の短縮化(2)現業務と分析テーマとの結び付けに本質的な課題があることが分かりました。

(1)分析時間の短縮化について、Classiでは容易に分析できるように各プロダクトチームのユーザビリティが高いDWH/DMを再構築をしています。

(2)現業務と分析テーマとの結び付け方については、非常に難しい問題ですが、データ活用を推進している我々がデータを活用する立場の彼らに寄り添い、業務を理解した上で共に考えていくことが大切なのだと思います。 そもそも「”基礎”的なデータ活用方法」というのは、”基礎”技術ではなく「業務のどこで活用するのか」という意味での”基礎”であるということが参加者のコメントからもうかがえます。”基礎”と聞くと、”基礎”技術を思い浮かべがちですが、非エンジニアの認識は必ずしもそうではないようです。 現在は、各プロダクトチームに対するヒアリングにより一層力を入れています。データAI部が分析した結果を提供し、その際に分析手法も伝えるようにしたり、施策打ち出しや機能リリースのタイミングを見計らって関係部署に「今が分析のタイミング」とお知らせする等しています。

また、その他の勉強会後の活動として、SQLの理解度アップのために、ClassiオリジナルのSQL練習問題を100問作成(通称:100本ノック)してトレーニングしてもらっており、そこでわからない問題については定期的なもくもく会で個別サポートを行っています。

データ活用ができていないというのは他の企業様でもよく耳にする課題かと思います。今回ご紹介した弊社のSQL勉強会の学びが参考になれば嬉しいです。

TDD研修 by t_wada さん を開催しました

こんにちは。エンジニアの原です。 先日、「テスト駆動開発」の翻訳者として知られるt_wadaさんこと和田卓人さんをお招きして、テスト駆動開発ワークショップの第1回目をオンラインで開催しました。 今回はその様子をお届けします。

きっかけ

Classiでは毎年外部講師をお招きして勉強会を行っています。 新卒研修を行う中で、「テストコードを実装する際の勘所」をどう伝えようかという話があり、第一人者のt_wadaさんをお呼びして研修を行っていただくことになりました。 そこで新卒以外にも参加希望者が多かったため、プロダクト開発部の誰でも参加できる全体の勉強会として開催することになりました。

事前準備

研修の定員が10名のところ、30人近い参加希望者がいたため、3チームに振り分けを行い、3回に分けて実施することにしました。 事前にt_wadaさんから参加者の自動テスト経験の有無と、自動テストで課題に感じていることについてアンケートを行って頂き、その結果をもとに研修内容のすり合わせを行いました。 参加者全員が自動テストの実装経験があるということで、TDDの前提知識はTDD Boot Camp 2020 Online #1 基調講演/ライブコーディング を事前に視聴してくる前提で、当日は質疑応答とライブコーディングを行うという反転学習形式で進めることになりました。

ClassiのSlackワークスペースにシングルチャンネルゲストとしてt_wadaさんをお招きして、事前に簡単なコミュニケーションが取れたため、当日スムーズにワークショップに入ることができました。

当日の内容

午前中は質疑応答、午後は演習を行いました。

午前の部

質疑応答では、事前アンケートで出た質問や、TDD Boot Campの基調講演に関する質問にお答えいただきました。

Classiの参加メンバーからは、

  • どのくらいテストを書くかの指標に code coverage を使うことが多いが他にどういう指標があるか?
  • 目標とすべきテストカバレッジはいくつか? といった質問が出ました。

それに対して

  • カバレッジに関して、テストがないプロジェクトではまずは65%を目指して、テストがあるプロジェクトでは85%を目指すといい
  • カバレッジ以外のプロジェクトの健全性を示す指標として静的解析ツールで複雑度を検証したり、Lintでフォーマットを統一したりすることができる

といった答えを頂き、目指すべきプロジェクトの姿を言語化することができました。 古くからあるプロジェクトにテストコードが無かったり少なかったりという状況を打開する指針になったと思います。 また個人的に印象に残っているのが、「TDDは実装コストは2割増えるが、不具合発生率は8割減らすことができる」という言葉で、TDDのメリットを伝えるのに最適な言葉だと感じました。

午後の部

午後の演習は、簡易的なECサイトの機能を実現するプログラムを以下のTDDのサイクルで書いていくという内容でした。

お題の詳しい内容は以下のリンク先で書かれています(t_wadaさんに許可を頂いて掲載しています)。 https://gist.github.com/twada/856c37103ebd3d1fb973ba2c2654f9d6

まずは与えられた仕様からTODOリストを作らないといけないのですが、仕様の整理の前にコードを書き始めてしまったり、いつもの癖が抜けずにいました。しかし、サイクルを繰り返すことでTDDの手順がしっくり来るようになり、予め目標を考え、その目標を示すテストを書いてからコードを書くことで、テスト容易性を考えられたシンプルなコードが最初から書けるようになったという実感がありました。

途中t_wadaさんとの1on1が複数回あり、TDDの実践方法に関するアドバイスやコードレビューをしていただいたのも貴重な経験でした。

コードレビューの様子
コードレビューの様子

終わりに

集合写真

定員が10名という少人数での開催だったため質問しやすく、1on1の機会もあり参加者全員がt_wadaさんとコミュニケーションを取ることができました。 t_wadaさんの圧倒的な知識の深さに常に圧倒される一日でした。

個人的な感想は以下のツイートの通りで、TDDは少しのコストで何倍ものメリットを享受できることを実感できたので、今後はTDDやっていくぞ!という気持ちで溢れています。

dron: クラウドネイティブなcron代替の紹介

みなさん、こんにちはこんばんは。Classiの基盤バックエンドチームでプロダクトや機能を越えてサーバサイドを中心に困り事を手広く解決する仕事をしているid:aerealです。

今回の記事ではClassiのパフォーマンス改善のため取り組んでいるdronと呼ばれるクラウドネイティブなcron代替 (Cloud Native Cron Alternative) の開発について、運用を見据えてどのような考慮を重ねたのかを紹介します。

背景と課題

dronの説明をするにあたって、現行の非同期処理システム (以下、現行システム) の用途と抱えている課題について簡単に紹介します。

現行のワークロード

主な用途はサービス内通知の送信です。AppleやGoogleのスマートデバイス向けプッシュ通知サービスへ適切なペイロードを送る仕事が大半です。

個々のジョブが要するコストは小さくとも、同時に大量に要求されるためそれなりに計算機資源を消費するという性質があります。

主な用途は通知と述べましたが、これを単純にat least onceな実行モデルのシステムに載せ変えると二重・三重に通知が送信されえます。 通知はトランザクション処理ほどクリティカルな影響をもたらしませんが、エンドユーザーをインタラプトする機能ですからナイーブな実装は利用者の負担にもなります。

加えて未来の時間に所与のペイロードでジョブを実行するという予約実行の仕組みがあり、これの移植も求められます。

課題

DBにやさしくない

Classiの各種データは中央のRDBMSで管理されており、各コンポーネントが読み書きしています。

Webテストの結果や校務記録などコアとなる機能のデータが集まっており、この中央DBの負荷が上がるとClassiの機能すべてに影響があります。 現行システムはこのサービスの根幹ともいえる中央DBをキューとして利用しています。

しかし、これは望ましい用途ではないと考えられます。

Webアプリケーションにおいてリクエストを受けてレスポンスを返すライフサイクルから外れて非同期で処理を実行するモチベーションは概ねレスポンスタイムの向上を図るという一点に集約されると言ってよいと思います。 しかし、データストアを共有していることから現行システムが中央DBを通じてClassiのサービス全体のパフォーマンス劣化を及ぼしうる状況にありました。

アプリケーションのレコードとJOINするようなこともなく同じDBにキューを用意する必然性は無いので、とにもかくにも中央DBの利用をやめたいところです。

スケールアウトの困難なアーキテクチャ

一般にキューを利用した非同期処理システムでは、実行されるジョブの性質に合わせてスケーラビリティを確保するためキューを分離したり、実行環境をスケールアップ・スケールアウトさせることがパフォーマンス改善の手として挙げられます。

現行システムは前述のように中央のRDBMSを利用しPHPで書かれた独自のデーモンとして実行されています。 排他処理は考慮されていないため、素朴にデーモンの実行数を増やすと競合が起きえます。

さらに悲しいことに実行されるジョブの実装もただ一度 (exactly-once) 実行されることを期待しているため、よしんばPHPのデーモンを改修したとしても数あるジョブの実装をひとつひとつ丁寧に排他制御しなければいけず現実的とはいえません。

方針

exactly-onceマナーでの実行、ジョブの予約の実現など複雑かつワーカーとして複雑な責務は凝集させ、各ジョブの実装 (= 知識) は各チームに担ってもらうことで梃子を効かせつつ分権を狙います。

そのために:

  • 各ジョブの実装はプライベートなHTTPエンドポイントとして再実装される
    • リクエスト、レスポンスの形式などアプリケーションプロトコル策定は基盤バックエンドチームが主導する
    • 再実装は各ジョブのオーナーを持つチームに依頼する
  • dronは各ジョブのHTTPエンドポイントを呼び出すところまでを責任境界とする

……とします。

設計

f:id:aereal:20210120172313p:plain
dronのシーケンス図

コンポーネント概説

Facade

  • クライアントが行える操作をHTTP APIとして提供する
  • API Gateway + Lambdaで実装される

Job Executor

  • ジョブを実行するStep Functionsのステートマシン
  • Lambdaの呼び出しが統合されているだけではなくDynamoDBのGetItem/PutItem/UpdateItemなどの各種API呼び出しがLambdaを起動することなく直接行えるのでコードの削減やレイテンシの点で優位性がある
  • 失敗可能性のある処理のリトライや状態遷移を移譲することでアプリケーションを素朴に保てる
  • 並列実行に制約はほぼなく、スケーラブルである

Job Scheduler

  • CloudWatch Eventsで毎時起動されるStep Functionsのステートマシン
  • Job Reservationからジョブ情報を取得、Job Executorを並列に実行する

Endpoint Data

  • Worker EndpointのURLを保存するDynamoDBのテーブル
  • 一度登録されたURLは変更できない
    • 新しいURLを用いる場合は新しいエンドポイントとして登録する
    • 過去のある時点のジョブがどのURLへアクセスしえたかを追跡しやすくするため
  • ジョブの実行要求ごとに任意のURLを受け取る場合、瑕疵により誤ったURLであってもその妥当性を判断できない
  • 事前にURLを登録し、ジョブの実行要求にはエンドポイントの識別子を含めると登録済みかどうかでtypoなどの瑕疵を発見できる
  • 将来的にこのテーブルを拡張して最高並列数を持たせたり、URLテンプレートとしてFacadeへの一度の実行要求から複数のURLへジョブを実行できる含みを持たせている

Job Data

  • ジョブの情報を保存するDynamoDBのテーブル
  • 一度挿入されたらクライアントの操作によって削除・更新はされない
  • 不変データとすることで過去の状態を再現する手間がなくなり調査がしやすくなる
  • Job Executorによってロックとして機能する実行中フラグと完了時刻は更新される

Job Reservation

  • ある日時に実行すべきジョブを保存するDynamoDBのテーブル
  • 日時を過ぎても各アイテムは更新や削除されないので、あるジョブが確かにある日時に予約されたかはあらゆる時点から追跡可能

Worker Kicker

  • Lambdaとして実装される
  • URLとHTTPメソッド、リクエストボディを与えられ適切なタイムアウトを設定してHTTPリクエストをWorker Endpointへ送信、レスポンスを返す

Worker Endpoint

  • 各ジョブの実際の処理を行うHTTPエンドポイント
  • Endpoint Dataは少なくともひとつのWorkerに対応する
  • 各利用者アプリケーションの一部として構築される
  • 条件を満たせばClassiが直接管理しないHTTPエンドポイントをWorker Endpointとすることもできる (例: Twitter API)

運用時の考慮事項

追跡・トレーシング

ジョブの追加や実行が成功したか・どのクライアントがどのジョブを追加したかといった情報は顧客の問い合わせに対応したりデバッグを目的としてX-RayによるトレーシングおよびStep Functionsによる実行ログに保存されるのでそれを利用できます。

ジョブの追加および実行の完全な追跡はX-Rayの制限により最大30日間に、ジョブの実行の部分的な追跡はStep Functionsの制限により最大90日間に制限されます。

命名

cronの次を目指す ('c'.succ + 'ron') ということでdronと名付けました。

これまでに述べてきた既存システムの置き換えではcronの定期実行という仕組みは必須ではなくまた現時点で実装もされていないにも関わらずcron alternativeを標榜しているわけは exactly-onceで処理を実行するシステムの難しさ に着目し、cronはこれを部分的にせよ解決していたことに由来します。

cronのexactly-once実現はSPOFを持ち込むことと表裏一体ですので、クラウドサービスを活用し可用性を高めつつ実現することで既存のワークロードからの容易さが社内の利用者に伝わることも願っています。

おわりに

システムのパフォーマンス改善という大きなミッションのためには、時には個々のコンポーネントの置き換えといった大胆な手段をとる必要があります。

こうした大胆な判断をとる意義と必要性を理解した上で、ニューカマーの私にプロジェクトのリードを任せてもらえる、攻めと守りにメリハリのついた健全な技術決定のできる組織になっていると実感し楽しく思っています。

サービスの改善という大目標への道程はまだ始まったばかりですし、もっと言えばマイルストーンはあっても終わりなどないのですが、それでも大事な一歩に少なからず貢献できたと自負しています。

付録: Cloud Native Cron Alternativeとわたし

元々筆者はqronというAWS上で上記に似たシステムを構築するためのAWS CDKライブラリを開発・公開しています。

ちょうどClassiに参加してどんなプロジェクトを担当しようかという話をする中で、この記事で挙げた課題・プロジェクトが候補に上がっており当初はレビュアーとして「こういった解決策があるよ」と提案する立場でしたが、先行してライブラリを作っていたこともあり主担当となりました。

qronについてYAPC Kyoto 2020で発表予定でしたが、COVID-19の拡大に伴いイベントの開催が見送られたこともあってこのアイデアについて紹介する機会が見つけられず残念に思っていたこともあり、実運用へ投入した事例を紹介できるならという思いもありました。

本当に偶然が重なった結果ですが数奇な巡り合わせを感じずにはいられません。 よって命名時にもqronをインスパイアした名前にしました。

Classiにフロントエンドエキスパートチームを作った話

こんにちは、Classiに入社して1年になるGoogle Developers Expert for Angularのlacolacoです。 今日はClassiに新しく フロントエンドエキスパートチーム を作った話を紹介します。

フロントエンドエキスパートチームとは?

日本のフロントエンド界隈(?)の方なら、フロントエンドエキスパートチームと聞いて真っ先に思い浮かぶのはサイボウズさんのチームだと思います。 Classiで新たに立ち上げたチームは、名前も含めてサイボウズさんのフロントエンドエキスパートチームをかなり強くインスパイアしています。 そのメンバーであり友人でもあるsakito君にはチームの設計にあたって相談に乗ってもらい、名前をそのまま真似ることも快諾してくれました。この場を借りて改めて感謝です!

speakerdeck.com

メンバー構成

2021年1月現在、lacolaco 1人のみです。

1人でチームを始めていいものか少し悩みましたが、サイボウズさんのフロントエンドエキスパートチームも最初はkoba04さん1人だったということを知って、決意が固まりました。

blog.cybozu.io

チームのミッション

Classiのフロントエンドエキスパートチームはサイボウズさんの先例に倣い、3つのミッションを掲げています。名前がまったく同じですがオリジナルの名前があまりにも優れていたため、変える理由が見つかりませんでした。

  • 支援
  • 探究
  • コネクト

支援

2020年、Classiはプロダクト開発に関わる組織体制を大きくアップデートしました。その詳細は元CTO現VPoEと、現VPoTが書いた過去の記事を参照ください。

tech.classi.jp tech.classi.jp

新しい体制では、クロスファンクショナルチームによってプロダクトを開発・運用するという色が強くなりました。 そしてクロスファンクショナルチームの常として、そのチーム内のメンバーだけでは解決が難しい課題に対して、横断的に各チームを支援する専門家が必要になります。

フロントエンドエキスパートチームは間接的あるいは直接的に各チームの課題を解決、または解決を支援することを第一のミッションとしています。 具体的には技術的な相談を受けたり、ペア作業・モブ作業に参加して指南したり、ドキュメントを書いたりといった内容になります。

探究

エキスパートチームがエキスパートチーム足るためには、その説得力を裏付けする専門性を養い続ける必要があります。 プロダクトの価値や開発品質の向上を目指し、フロントエンド領域において技術調査・検証を行い、プロダクト開発への導入を推進します。

コネクト

このミッションは名前はそのままですが、オリジナルの内容から少しアレンジを加えました。 フロントエンド領域について、社内のエンジニアと社外のエコシステム・コミュニティをつなぐハブとしての役割を、フロントエンドエキスパートチームの3つ目のミッションとしました。 オリジナルでは主に内から外への発信にフォーカスされていましたが、われわれのコネクトでは外から内への方向にもコネクトがあると再定義しました。

外から内へ、つまり社内勉強会や技術ブログなどの啓蒙のような活動になりますが、開発業務以外での技術習得の機会を増やすことが主な目的です。 外向きの発信が価値あるものとなるように、まずは世界を知ること。業務上の知識だけで井の中の蛙になってしまわないよう、社内だけでなく社外からみても通用するスキルを養う機会を増やせるといいと思っています。

いま向き合っている課題

Classiのシステムはまだまだ課題だらけなのは過去の記事でも述べられている通りですが、フロントエンド領域も特に課題が山盛りです。 それでも各チームが危機意識を持って改善に取り組んでおり、半年とは思えない成果が出ています。代表的なものをいくつか紹介します。

  • AngularJSの利用箇所をLTSバージョンのv1.8系へほぼすべてアップデート完了
    • 複数のアプリケーションでv1.2や1.3、1.5などが混在する状態でした
  • jQueryの利用箇所を最新バージョンのv3.5系へほぼすべてアップデート完了
    • 複数のアプリケーションでv1系とv2系がほとんどでした
  • AngularJSからAngularへの段階的移行が複数のアプリケーションで進行中
    • builderscon 2019で発表したAngular Elementsを使ったコンポーネントレベルリプレースを実践しています
    • すでにいくつかのアプリケーションでは本番環境でも動作中です

これらはすべて各プロダクトチームが熱心に取り組んだ結果で、頼もしい限りです。 フロントエンドエキスパートチームはこれらの課題を解消するサポートをしつつ、このような状況を将来に渡って生まれにくくするための全体の底上げを目指しています。

2021年の大きなテーマは、フロントエンドの運用面で当たり前にやるべきことを定義し、これまで軽視されてきたことを再認識してもらい、各チームで当たり前との差分に意識を向けてもらうよう推進していくことです。

f:id:lacolaco:20210107174735p:plain
全アプリを俯瞰したフロントエンド運用まるわかりシート

おわりに: FrontendOps文化を目指して

Webアプリケーション開発において、高速で信頼性の高い魅力的なユーザー体験を構築するための複雑性の重心がサーバーからクライアントへ移ってきていることは間違いありません。これが、フロントエンドの「運用」を重要視している理由です。

その流れの中で、FrontendOpsの考え方は、これまでブラックボックス的に扱われてきた「フロントエンド」という領域を解体します。少なくともここではフロントエンド開発とフロントエンド運用という領域を分けることができます。これはバックエンドでデータベースエンジニア、アプリケーションエンジニア、運用エンジニアなどの役割が生まれたのと同じことです。FrontendOpsはwebpack職人の代名詞ではありません。アプリケーションをユーザーへ届ける領域にフォーカスした専門領域です。

どんな機能や体験もフロントエンドを通してのみユーザーに届きます。にもかかわらず、フロントエンドの運用とそれがユーザーへ与える影響の理解が、フロントエンドエンジニアだけの関心になってきました。これを解消してフロントエンドの運用をチームの関心事とする意識の持ち方がFrontendOps文化 です。 この文化はDevOpsがそうであるように、開発者だけが考えることではなくチーム全体に求められるものです。「チームの全員が、フロントエンド運用が最終的なユーザー体験にどれだけ影響を与えるかを理解しておく」FrontendOps文化の本質はこの一点です。チームのメンバーが実施のための専門性を備えていなくても、重要性を理解して真剣に取り組みたいというマインドがあれば、チーム外の専門家と共に歩むことができます。

Classiも例に漏れずこれまでフロントエンドの運用が軽視されていました。ユーザー体験を中心にチームが駆動されるように、フロントエンドエキスパートチームの取り組みとしてFrontendOps文化の醸成を推進しようとしています。短期間で成果が出るものではないので、腰を据えて取り組んでいます。 こんな仕事に興味のある方はぜひ力を貸してほしいです。フロントエンドエキスパートチームはいつでも頼もしい仲間を募集しています!

リモート環境下でのチーム作り

フロントエンドエンジニアの笠原です。こんにちは。 新型コロナウイルスの影響で「全員在宅勤務」のアナウンスが出てから早くも 9 ヶ月近くが経ちました。 その間にプロダクトチームの体制が再構築され、チームメンバーの変更もありました。

この数ヶ月の間にリアルで顔を合わせずに集まったメンバーで始まり、そして完了したプロジェクトを経験しました。 今回はそのときのプロジェクトでチームがどう移り変わっていったか?というお話をします。

チームの移り変わり

あとから思い出したときに、その時々の状況がタックマンモデルに近いと感じたので(少し無理やりですが) 4 つの段階にわけて書いていきます。

形成期

ディレクター 2 人と開発メンバー 3 人の計 5 人でスタートしました。 プロジェクトの内容は Classi の中でも校務系と呼ばれているプロダクトの機能追加でした。詳細は内容の簡単のために割愛します。 個人的には慣れたプロダクトでもなければ、一緒に働いたことのあるメンバーもいなかったのでかなりそわそわしていたのを覚えています。 MTG は Google Meet で行っていましたが、それ以外は Slack のテキストコミュニケーションが多かったように思えます。 プロジェクトの概要は決まっていたので、ディレクターから開発メンバーへのインプットを行う MTG が多い時期でもありました。 困ったときに少しでも話しかけやすい雰囲気にするために下記をワーキングアグリーメントととして決めました。

  • Slack で開始 / 終了時のあいさつをする
  • Google Meet で朝会をする
  • Google Meet で15 時におやつたいむという名の雑談時間を設ける

毎日顔を合わせる時間が持てたので、割とすぐに雑談はできるようになっていた気がします。

混乱期

雑談レベルのコミュニケーションが取れるようになってきて、プロジェクトのインプットもできてきたころ、スクラム開発を導入してみました。 慣れないチームで、ある程度スケジュールも決まっているプロジェクトだったのでスクラムの各種イベントを設定したほうが一週間のリズムができていいのではないか?というのが理由でした。開催したスクラムイベントは下記の通りです。

  • デイリースクラム
  • スプリントプランニング
  • スプリントレビュー
  • レトロスペクティブ
  • プロダクトバックログリファインメント

またこの頃からバックログを作り始めました。プロダクトバックログとスプリントバックログには Asana を使用し、それぞれ Asana のプロジェクトをわけて運用していました。 バックログの管理の仕方やスクラムイベントの進め方などは、以前僕が所属していたチームのやり方を持ってきました。そのため他のメンバーは慣れるまで覚えることが多かったかもしれません。 また、決して余裕のあるスケジュールではなかったので各ストーリーどこまでできたら done とするのかを決める「完成の定義」を作ったり、いざというとき何を優先するか(何を諦めるか)を決める「トレードオフスライダー」を作ったりしました。これによってみんながどういう風に考えているかや、チームとしての共通認識が深まったと思っています。

2 スプリントくらい回したところでスプリントレビューの場に QA メンバーの知見も欲しいという意見が出てくるようになりました。もともと QA メンバーはチーム外にいて、リリース前の検証で初めて関わる役割だったのですがスクラムチームのメンバーとして入ってもらうようにしました。QA メンバーがチームに入ることによりスプリントごとに検証の観点を QA 視点で確認できるようになり手戻りが少なく済みました。同じタイミングで開発メンバーももう一人くらいいると円滑に動けそうだったので、ここで QA メンバー +1 / 開発メンバー +1 の計 7 人になりました。(少しあとのタイミングでディレクターの一人が別案件に移ったので最終的には 6 人で進めました)

開発面ではモブプロを始めてみました。最初はどのくらいの頻度でやるほうがいいのか探り探りだったこともあり、「午後の数時間だけやる」とか「困ったらやる」といった感じでした。しばらく経ってくると「モブプロ良い」というコメントがレトロスペクティブで出るようになってきました。

f:id:kasaharu:20201208182026p:plain
レトロスペクティブのボードでモブプロが取り上げられる様子

一方で自分たちの作業時間を把握できていなかったり、「完成の定義」や「トレードオフスライダー」を作る時間を取ったりしてプランニングで決めたことが全部終わらない、という週が少し続いていたりもしました。

統一期

最初は少しずつ始めたモブプロでしたが、この頃には朝会が終わってすぐに Slack call でモブプロをするようになってきました。今回のプロジェクトでは詳細にベロシティの記録をしていなかったので感覚値になりますが、モブプロになったことでスピードが落ちたと感じることも、そのように指摘されることもありませんでした。逆にみんなで一つずつタスクを完了にしていく進め方になったのでどのタスクも自分ごとに捉えられて「終わらせるぞ」というモチベーションが上がっていたようにも思えます。

また今までは一通り実装を追えたあと、ステージング環境にデプロイしてディレクターに確認を依頼していたものを、軽微なものはローカル環境で実装中の画面キャプチャを見てもらう、といったように気軽にコミュニケーションが取れるようになってきてフィードバックを早くもらう工夫もできてきました。

進捗も安定してきてスケジュール感が把握できるようになってきました。これによって、すべての機能を作ってからのリリースではユーザーが使いたいタイミングに間に合わない、といった話も出てきて一部の機能だけ先行リリースをするといった決定もできました。

機能期

このころは開発メンバーだけでなくディレクターや QA メンバーを巻き込んで開発するのが日常的になっていました。 開発メンバーがモブプロで実装している途中で、ちょっとした疑問や相談事ができた場合にはすぐにディレクターを Slack call に呼んで作りかけの画面を見せながら相談をしたり、逆にディレクターや QA に相談ができたときは開発メンバーが作業している Slack call に気軽に入ってきて話しかける、という動きができていていい意味で遠慮がなくなってきました。

f:id:kasaharu:20201208182346p:plain
ディレクターに相談したらすぐに Slack call に入ってくれる様子

またレトロスペクティブのときに「みんなが同じくらい発言するようになってきた」というようなコメントも出てチームとして成長しているんだと実感したのを覚えています。

最後は無事予定通りリリースすることができました。

まとめ

僕自身、全員リモートワークになるまでは基本オフィスに出社していたためリモートワークに慣れておらずチーム作り新チーム体制に結構な不安がありましたが、リモートワークでもチーム作りはできるんだという学びを得ました。 今は様々なサービスにあふれていてオンラインでもツールに困ることも少なかった気がします。

すべてが終わったあとに開催した振り返り会では「チームの人数がちょうどよく、皆がこのプロジェクト専任だった」・「すぐ話せたり、カメラオンでリアクションがありコミュニケーションが密だった」といったコメントがありこの辺りもいい効果を生んだのではないかと思っています。

リモートワークが基本の会社も増えてきていると思うので、もしリモート環境下でのチーム作りに悩んでいる人の参考になれば幸いです。

教育業界に数理最適化技術で価値をもたらしたい

みなさん、こんにちは。 データ/AI部でデータサイエンティストをしております廣田と申します。今回は家庭訪問を題材とし、教育業界への数理最適化技術の応用について語ってみたいと思います。「データサイエンティストなのに数理最適化の話?」と疑問に思われた方もいらっしゃるかと思いますので、まずはこの点についての補足から話を始めさせていただければと思います。

なぜ数理最適化の話をするのか

データ/AI部ではこれまで、サービスの利用ログや学習コンテンツデータ・アンケートデータ等、多種多様なデータを駆使して教育業界に様々な価値を生み出してきました。

その一方で、データ活用ではなく数理最適化技術で解決できる見込みのある課題にも数多く遭遇してきました。例えば時間割の自動作成問題などが挙げられます。これまではチームのリソース不足などの事情もあり、このような課題についてはなかなか検討が進められておりませんでした。最近になって本格的に検討が進められる体制が整ってきたため、今後バリバリ数理最適化技術も活用して価値を生み出していこうという決意表明もかねて、こちらのブログで数理最適化の話題を扱おうと考えた次第です。

ここでは読者に教育現場の課題を数理最適化技術で解決するイメージを具体的に持っていただくため、具体的な問題を1つ取り上げてみたいと思います。前述の時間割の自動作成問題については既存研究が数多くあるため、ここでは(筆者調べで)あまり見かけないテーマとして「家庭訪問」を取り上げたいと思います。

家庭訪問について

家庭訪問で学校の先生が自宅に訪問してきた経験、皆様はございますでしょうか。訪問される側は経験していても、訪問する側の視点に立つことはなかなか無いと思います。そこでここでは訪問する側、つまり先生側の視点で家庭訪問を見てみたいと思います。

先生が家庭訪問を行う主な目的としては

  • 保護者と1対1でコミュニケーションをとる機会
  • 児童・生徒の家の位置や周辺の環境の確認

が挙げられます。そのため、1家庭の訪問でもそれなりに時間が必要となります。小中学校において1クラスあたりの生徒・児童数は30人前後であることが多い1ため、1日で全家庭を訪問することは非常に困難です。その場合、数日に分けて全家庭を訪問をすることになります。そこでまず先生は各日にどういう順序で家庭を訪問するかを自力で大まかに決め、必要に応じて各家庭と個別に日程調整をした上で実際に訪問することになります。

ここで、この訪問計画の案出しが問題になります。クラスによって生徒・児童の家の位置の分布は異なるため、クラスごとに検討が必要です。全家庭が学校から近い位置にあれば、どういう順序でも先生の負担はそれほど変わらないでしょう。しかし、学校から遠い家庭が複数あるような場合では、回り方を工夫しないと先生の移動がかなり大変なものとなります。どうすれば良い塩梅で決められるか・・・?そこで数理最適化技術の出番となるわけです。

家庭訪問の訪問計画問題

まず解きたい問題を整理します。ここで最適化問題に落とし込むのにあたり、本記事では総移動距離が短い回り方を求めることを目的とすることにします。

  • 入力
    • 2地点間の距離(学校及び生徒の自宅の全ペアについて事前計算しておく)
  • 制約
    • 家庭訪問期間の日数
    • 1日で訪問できる最大の家庭の数(各家庭で話す時間から概算)
    • 各日のルートは、学校から出て学校に戻ってくるものとする
  • 出力
    • 総移動距離が短くなるような、各日における家庭の回り方

このように整理すれば、解きたい問題は「容量制約付き配送計画問題(Capacitated Vehicle Routing Problem、CVRP)」であることが見えてきます。

CVRPについての説明はこちらの記事がわかりやすいと思います。こちらの記事で扱っている問題は「1箇所の倉庫に集められた荷物を複数のトラックを使って各店舗に配送する際に、トラックの総移動距離を最小化したい」というものですが、それと今回の問題を下記のように対応付ければ、今回の問題がCVRPになることはイメージできるかと思います。

トラックを用いた配送計画 家庭訪問の訪問計画
1台のトラック ある日の先生
トラックの台数 家庭訪問期間の日数
トラックの最大積載量 先生が1日で訪問できる家庭の数
各トラックは倉庫から出発し、要望のあった荷物を各店舗に配り、倉庫へ帰ってくる 先生は各日において学校から出発し、各家庭を1回訪問し、学校へ帰ってくる
全トラックの移動距離の合計を最小化 期間中の先生の移動距離の合計を最小化

それでは実際に解いてみます。今回の問題のようによく知られた問題に対しては、それを解くためのライブラリが整備されていることも多いです。ここではGoogleの数理最適化ライブラリ OR-Tools を使ってみることにしましょう。 OR-Tools を利用してCVRPを解くサンプルコードがこちらに載っていますので、参考にして python でコーディングしていきます。 下記のコードは「1日7家庭までの制約の下で31家庭を5日かけて訪問する計画を立てる」といったコードです。実行前に pip install ortools networkx matplotlib で、必要なライブラリをインストールしてください。コードの詳しい説明は省略しますが、前述のサンプルコードにほぼ準拠しています。サンプルコードとの主な差異は2点で、問題用のデータセット生成処理と、結果の画像出力処理が追加されています。2地点間の移動距離については簡易的に直線距離をベースに計算しています。

from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
import matplotlib.pyplot as plt
import networkx as nx
import math


def create_data_model():
    nodes = [
        {'name': 'School', 'coord': (0, 0)},
        {'name': '1', 'coord': (-330, 200)},
        {'name': '2', 'coord': (-300, 300)},
        {'name': '3', 'coord': (-220, 250)},
        {'name': '4', 'coord': (-50, 100)},
        {'name': '5', 'coord': (20, 100)},
        {'name': '6', 'coord': (150, 220)},
        {'name': '7', 'coord': (260, 170)},
        {'name': '8', 'coord': (370, 40)},
        {'name': '9', 'coord': (420, 20)},
        {'name': '10', 'coord': (-390, -60)},
        {'name': '11', 'coord': (-370, -200)},
        {'name': '12', 'coord': (-300, -110)},
        {'name': '13', 'coord': (-250, -150)},
        {'name': '14', 'coord': (-50, -150)},
        {'name': '15', 'coord': (10, -300)},
        {'name': '16', 'coord': (-50, -250)},
        {'name': '17', 'coord': (80, -180)},
        {'name': '18', 'coord': (200, -200)},
        {'name': '19', 'coord': (300, -120)},
        {'name': '20', 'coord': (370, -150)},
        {'name': '21', 'coord': (450, -100)},
        {'name': '22', 'coord': (-300, 120)},
        {'name': '23', 'coord': (-150, 60)},
        {'name': '24', 'coord': (-100, 20)},
        {'name': '25', 'coord': (450, 370)},
        {'name': '26', 'coord': (250, 50)},
        {'name': '27', 'coord': (-250, -20)},
        {'name': '28', 'coord': (-50, -60)},
        {'name': '29', 'coord': (120, -100)},
        {'name': '30', 'coord': (140, -10)},
        {'name': '31', 'coord': (180, -50)}
    ]
    n_nodes = len(nodes)  # = 32
    school = 0  # points内の学校のインデックス
    n_days = 5  # 家庭訪問期間は5日間
    capacities = [7 for _ in range(n_days)]  # 各日で回れる家庭の数は7
    colors = ["r", "g", "b", "c", "m"]  # 可視化の際に利用する色(各日ごとに1色を対応付ける)
    distance_matrix = [[0 for _ in range(n_nodes)] for _ in range(n_nodes)]
    for from_node in range(n_nodes):
        from_coord = nodes[from_node]['coord']
        for to_node in range(n_nodes):
            to_coord = nodes[to_node]['coord']
            # 移動距離は簡易的に直線距離ベースで計算
            distance_matrix[from_node][to_node] = \
                math.sqrt((from_coord[0] - to_coord[0]) ** 2 + (from_coord[1] - to_coord[1]) ** 2)
    data = dict()
    # (1) or-toolsに投げる際に必要な情報
    data['distance_matrix'] = distance_matrix
    # 各家庭に1回訪問(「各店舗に1つのものを配送すること」と対応)
    data['demands'] = [1 if n != school else 0 for n in range(n_nodes)]
    # 1日に回れる家庭数(「1台のトラックで運べる物の量」と対応)
    data['vehicle_capacities'] = capacities
    # 家庭訪問の日数(「トラックの台数」と対応)
    data['num_vehicles'] = n_days
    # 学校(訪問の起点)(「トラックの出発点となる倉庫」と対応)
    data['depot'] = school
    # (2) 可視化のために必要な情報
    data['nodes'] = nodes
    data['colors'] = colors
    return data


def draw_solution(data, manager, routing, solution, fig_name='output.png'):
    G = nx.DiGraph()
    nodes = data['nodes']
    G.add_nodes_from([node['name'] for node in nodes])
    for vehicle_id in range(data['num_vehicles']):
        # 各日(「各トラック」と対応)のルートを描画
        index = routing.Start(vehicle_id)
        while not routing.IsEnd(index):
            previous_node_index = manager.IndexToNode(index)
            index = solution.Value(routing.NextVar(index))
            node_index = manager.IndexToNode(index)
            G.add_edge(
                nodes[previous_node_index]['name'],
                nodes[node_index]['name'],
                color=data['colors'][vehicle_id])
    pos = {
        node['name']: node['coord'] for node in data['nodes']
    }
    edge_color = [edge['color'] for edge in G.edges.values()]
    nx.draw_networkx(G, pos=pos, arrowsize=15, edge_color=edge_color, node_color='c')
    plt.savefig(fig_name)
    print(f'Result: {fig_name}')


def main():
    # 今回解く問題のデータを生成
    data = create_data_model()

    # 探索に必要なデータをセット
    manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
                                           data['num_vehicles'], data['depot'])
    routing = pywrapcp.RoutingModel(manager)

    def distance_callback(from_index, to_index):
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)
        return data['distance_matrix'][from_node][to_node]

    transit_callback_index = routing.RegisterTransitCallback(distance_callback)
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

    def demand_callback(from_index):
        from_node = manager.IndexToNode(from_index)
        return data['demands'][from_node]

    demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
    routing.AddDimensionWithVehicleCapacity(
        demand_callback_index,
        0,
        data['vehicle_capacities'],
        True,
        'Capacity')

    # 探索時の戦略をセット
    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
    search_parameters.local_search_metaheuristic = (
        routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
    search_parameters.time_limit.FromSeconds(1)

    # 探索&得られた解の可視化
    solution = routing.SolveWithParameters(search_parameters)
    if solution:
        draw_solution(data, manager, routing, solution)


if __name__ == '__main__':
    main()

実行してみると下記のような結果が得られます。各日に回るルートが同じ色の矢印で表現されています。見た目的にも納得感のある結果が得られたのではないでしょうか。

f:id:hiro_makimaki:20201207145003p:plain
得られた結果

ひとまず解を得ることはできましたが、様々な課題が残っています。このような課題については、実際に現場の先生や専門家と意見を交えながら解決策を探っていくことになります。

  • 目的関数や制約の設定の妥当性
    • 学校や先生によって重要視する点は異なるはず
  • 移動時間が不正確
    • 実際の移動時間は直線距離でなく道のりに基づいて計算するべき
    • また、学校から遠く徒歩・公共交通を併用する必要がある家庭の場合は、その移動手段の違いも考慮して移動時間を計算するべき
  • 特定の日時でしか家庭訪問の対応ができない家庭があった場合、その事情をどう解に組み込んでいくか

最後に

ここまで家庭訪問を題材に数理最適化の話をしてきましたが、そもそも最近は家庭訪問を行う学校・先生の数は減少傾向にあると耳にします。両親共働き家庭の増加や新型コロナの影響など様々な要因により、平日の昼間に学校の先生と保護者がオフラインコミュニケーションを行うことは困難になりつつあります。ではなぜこのような下火になりつつある題材を取り上げたのかと言えば、これまであまり光が当たらなかった話題であり、かつ数理最適化技術の面白さが光る話題だと考えたためです。

今回取り上げた題材をはじめとして、教育業界にはまだまだ数理最適化技術の活躍の余地が広く残されていると考えております。学校関係者及び数理最適化の研究者の方、教育現場を進化させるために何ができるか、一緒に考えていきませんか?

また、弊社ではミッション「子供の無限の可能性を解き放ち、学びの形を進化させる」に共感し、様々な課題に前向きに取り組んでいただける仲間を募集しております。興味を持たれた方はぜひ採用ページからご連絡ください!


  1. 文部科学省の令和元年度学校基本調査における収容人員別学級数のデータを見てみると、小学校では26~30人・中学校では31~35人のクラスの割合が最も多いです。こちらの記事に載っているグラフを見るとわかりやすいと思います。

© 2020 Classi Corp.