Classi開発者ブログ

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

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人のクラスの割合が最も多いです。こちらの記事に載っているグラフを見るとわかりやすいと思います。

セキュリティインシデントと大規模障害を経てClassiは開発組織をどう変化させたのか

こんにちは、元テックリード、この10月からVPoT(Vice President of Technology)に就任した、Classiの丸山(id:nkgt_chkonk)です。

前回の記事では、前CTO(現VPoE=Vice President of Engineer)の佐々木が「Classiで発生した2つの問題を繰り返さないために我々が取り組んでいること」というタイトルで、社内体制を変更したことをお伝えしました。

今回はわたしから、組織体制の変更の背景や、現在どのような体制で、どのような課題に取り組んでいるのかをさらに詳しくお伝えしたいと思います。

4月~5月にかけての2つの問題の原因となった「甘え」

前回の記事でお伝えしたとおり、2020年の4月〜5月にかけて、Classiは外部の攻撃者による不正アクセスおよびデータの漏洩という大きなセキュリティインシデントと、アクセス増による大規模なアクセス障害を起こしてしまいました。その根本的な原因は、長い間取り組むべき課題の優先順位を間違えてしまっていたというひとことに尽きると思っています。

なお、セキュリティインシデントの直接の原因については、すでに対策を講じており、外部のコンサルタントの協力を得て、さらなる改善を継続的に行っております。この記事では、遠因となった組織体制の問題について述べたいと思います。

アクセス障害の背景として、Classiは学校が導入し、年間を通じて使っていただくサービスであるという特性上、突発的にユーザー数が大きく増減することのないサービスでした。そのため、たとえば、他のコンシューマアプリなどのように、テレビで紹介されていきなりアクセスがスパイクする、というような急激なアクセス増加の蓋然性は低い、という「甘え」が、技術上の意思決定層に存在していました。実際には、今回の新型コロナウイルス感染拡大に伴う学校一斉休校のように、社会の状況が変われば、サービスの使われ方は変わり、負荷のかかり方も大きく変わることが生じてしまったわけです。

さらに、今となって問題を振り返ってみると、当時から「もっと既存のサービスを改善していくべきだ」「もっと投資のバランスを検討したほうがいいのでは」という声が、社内の開発現場からしっかりと上がっていたにも関わらず、きちんと現場の課題を見極めることができず、先延ばしの意思決定してしまっていたことに対して、後悔の気持ちでいっぱいです。

CTOの果たすべき役割の変化を認識できなかったという反省

なぜこのような状況を生み出してしまったのかを改めて考えたとき、わたしたちは「サービスのフェーズが変わって、それに応じて意思決定のあり方も変化していかなければならなかったのに、変化できていなかった」ということに気づきました。

佐々木のCTO就任当時は、ふたつの大きな課題が存在していました。ひとつは「学校 x ICT」というのは今と比べても未知なことが多く、学校現場に求められているサービスがどんなものなのかまったくわからないし、それを理解しにいくための足がかりすらない中でサービスを作らなければならなかったこと。もうひとつは、サービス開発のノウハウが社内にほとんどない中で、手探りでサービス開発を進めなければならないこと。

このふたつの課題に対して必要となるCTOの役割は、強い推進力であったと思います。なにがなんでもサービスを形にして、まずは協力してくださる学校に使ってみてもらうこと。ゼロからはじめて、オーナーシップを持って意思決定して、やり遂げる力こそが、CTOに求められる役割でした。その中で、佐々木は替えの効かない役割を果たしてくれたし、その結果として、多くの生徒、先生、保護者のみなさまにサービスを届けられるようになったことを、わたしは(あとから入ったメンバーではありますが)誇りに思っています。

しかし、サービスの拡大にともない、開発に関わるメンバーが増えるに応じて、CTOが本来担うべき役割は、わたしたちが気づかないうちに変化していました。多くの機能が存在するサービスに対して、一人ですべての意思決定を行うことはできませんし、技術的な課題に対しても目が届かなくなっていきます。本来であれば、サービスの拡大に伴い、開発現場の課題をよくわかっているメンバーに意思決定を移譲していかなければならなかったところ、オーナーシップを持って意思決定してやり遂げる役割のまま、CTOに意思決定が集中するという状況を生み出してしまいました。どのような課題に取り組むか? そのために誰になにをやってもらうか? そのために必要な人材の採用・育成は? という全てをCTOが決定する形になっていたわけです。

その構造が、開発現場から「これはまずい」という声が出ているにも関わらずそれを吸い上げきれない、という状況を生み出してしまったし、アクセス障害などの緊急時に意思決定のボトルネックとなってしまったと感じています。

意思決定をプロダクトチームへ移譲

以上のような反省のもと、最初は全ての機能、すべてのコードに対して、どのプロダクトチームが責任を持つかを決めることから体制の再構築をはじめました。その上で、各チームにチーム責任者と、テックリードという役割を置きました。

チーム責任者は、一般的にプロダクトオーナーと呼ばれる存在に近く、自分のチームの責任範囲にあるサービスで取り組むべき課題の優先順位を決定する責務を負います。

テックリードは、技術的な「やばいところ」に目を光らせ、チーム責任者に対して技術的な課題の優先順位を提案します。今までは、テックリードは全体を見る役割でわたしひとりだったのですが、今回テックリードの役割を再定義し、それぞれのチームの中の技術に責任を持つように変更した形です。

また、各チームが抱えている課題を解決するために必要な開発プロセスの改善や、採用戦略についても、プロダクトチームが一人称で動いていけるように、権限の移譲を進めています。

そのような体制を作っていきながら、何度も現場のメンバーに今なにに困っているかや、最近よかったことをヒアリングをしました。その結果、現体制の良い点として、ようやく技術的にもサービス的にもプロダクトを改善するために力を使えるようになったという声を聞くことができ、この点に関しては一定の成功を収めていると言えそうでした。

一方、「チームの中で技術的な決定をしていくことに不安がある」「プロダクト改善をはじめた結果、別のところに組織的なボトルネックを感じはじめている」「プロダクトチーム内で意思決定するとはいえ、会社としての意思決定の軸がなくて困っている」「このプロダクトの価値を判断するための会社としての指針が欲しい」というような声も上がってきました。その他にも、「自分の伸ばしたいことと今やっていることに乖離がある」「仕事の仕方を相談できる相手がいない」などの、仕事のしやすさや働き方についての問題点を指摘する声も聞こえてきました。

責務を見直してVPoTとVPoEによるスケーラブルな体制を構築

ちょうどその頃、佐々木と丸山を中心に「CTOの役割の再定義が必要」という課題に取り組んでいたこともあり、「こういう、プロダクトチームの内部だけでは解決できない問題に対して、解決となる構造を与えるのが、現在のClassiに求められるCTOの役割ではないか」という議論を進めていきました。その結果として、

技術的な視点として:

  • 各プロダクトチーム間にトレードオフが生じるときに、そのトレードオフをどこに落とし込むのかを決める意思決定の役割
  • テックリードが迷った時の駆け込み先
  • Classi全体の技術的アセットを健全に保つ仕組みづくり

というような役割を、

また、メンバーマネジメントの視点として:

  • CTOや部長が少ない人数で全体を見るのではなく、各メンバーに対してエンジニアメンターを置き、さまざまな悩みに対して最初の相談先を明確にする
  • そのエンジニアメンターが拾い上げた相談ごとのうち、組織として対応が必要なものを解決する仕組みや構造を作る役割
  • エンジニアが成長し、キャリアを積んでいける仕組みづくり

というような役割を、今のClassiのCTOは果たすべきではないか、と仮定しました。

この仮定をもとに佐々木と協議したところ、ふたりは得意分野が異なり、わたしはメンバーマネジメントが得意ではないけれど、技術的な視点では役にたてるところがあると感じていること、一方で佐々木は、メンバーマネジメントの分野で多くの現場メンバーから期待されていることが見えてきました。

そこで、今のClassiでは、CTOの役割を

  • 技術を見るVPoT
  • 人を見るVPoE

に分割した上で、VPoTを丸山、VPoEが佐々木が担う体制を作り上げました。

この体制の運用は始まったばかりで、まだ何がうまくいって何がうまくいかないかは十分には見えてきていません。VPoTとVPoEにはこういう役割が必要なのではないか、と立てた仮説も、まだ十分に検証されてはいません。一方で、この方向に改善を進めていくこと自体に間違いはないという手応えも得つつあります。

ClassiのMissionである「子供の無限の可能性を解き放ち、学びの形を進化させる」を実現するために、仮説を立てて終わりではなく、それで本当に子供の無限の可能性を解き放ち、学びの形を進化させるためのプロダクト作りに寄与できているのか? ユーザーのみなさまの学びの機会を奪ってしまうような失態を繰り返さないための構造を作れているのか? ということを定期的に問い直し、前向きに組織の改善を続けていきたいと思っています。

終わりに

Classiは、大きな失敗により、多くの先生・生徒・保護者のみなさまに多大なるご心配と迷惑をおかけし、多くの信頼を失ってしまったと思います。しかしながら、当社のメンバーは「子供の無限の可能性を解き放ち、学びの形を進化させたい」と本気で強く思っており、この思いの強さには自信があります。このMissionに向かっていくためには、一つひとつ、いまある課題を解決していく必要があると思っています。わたし自身、新しいポジションについたばかりで、毎日失敗を繰り返しながら、すこしずつ学びながら課題に向き合っています。

そして、これらの課題に取り組み続けるためには、今後も多くの力がClassiには必要になってきます。そのため、今、Classiでは、この大きなチャレンジに力を貸してくれるエンジニアを、積極的に募集しています。決して天国のように整った環境ではなく、むしろたくさんの成長痛をともなうような現場ではありますが、手強く解きがいのある課題が盛りだくさんで、自分が手を動かせば動かしただけ課題を解決できる、力を振るいたいエンジニアにとってはその力を存分に発揮できる現場ですし、「天然物」の課題に取り組む中で大きく成長できる現場だと思っています。

Missionに共感していただけて、腕に自信のあるエンジニアはぜひ、採用情報からご一報いただけるとうれしいです。一緒に課題を解決していきたいです。

Classiで発生した2つの問題を繰り返さないために我々が取り組んでいること

はじめに

こんにちは、Classiの佐々木(@sasata299)です。前回の投稿からだいぶ間が空いてしまい、季節はすっかり秋になってしまいました。

この期間、Classiでは外部の攻撃者による不正アクセス(4月)があり、また、サービスの高負荷によるアクセス障害(4月・5月)が続くなど、利用者の方々には多大なるご心配とご迷惑をおかけしました。この9月までClassiのCTOとしてサービスとお客様に向き合ってきた立場として、改めてお詫びを申し上げます。

本記事では、この2つの問題について改めて振り返りると共に、当時の状況と今我々がどのように対策を進めているのかについてお伝えします。
(※当社のCTOの変更については、後日改めて記事を出す予定です)

外部の攻撃者による不正アクセスが発覚(4月)

「Classi」は、学校現場の多様なシーンで活用されるクラウドサービスです。スマートフォン・タブレット・PCなどのデバイスを問わず利用でき、先生・生徒・保護者をつなぐサービスとして、主に高校(一部中高一貫校を含む)を中心にサービスを提供しています。

「Classi」は、4月5日(日)夕刻に予期しない事象が発生し、緊急でサービスを停止しました。不正アクセスの疑いを含めて調査を開始し、外部専門会社の協力も得てログの解析等を行ったところ、外部の攻撃者により不正アクセスが行われていたこと、この不正アクセスにより閲覧された可能性(のちに漏洩であることが判明)のある情報の範囲とその内容が11日(土)に確定し、13日(月)に全国のお客様に対して公表しました。

高負荷によるアクセス障害が発生(4月・5月)

3月から4月にかけては、新型コロナウイルスの感染拡大に伴い、全国の小中学校と高校、特別支援学校への臨時休校要請もあり、「Classi」の採用校も一斉休校となりました。 皆さんご存知のとおり、Beforeコロナの学校はほぼオフラインが前提でした。それが突然、学校は休校となり、オンラインにて学校運営を行わざるを得ない状況となりました。

当時、学校現場やそれを支援する企業側でどのようなことが起きていたのかについては、多くの先生方やメディアが発信されているので、そちらをご覧いただければと思います。


一方、「Classi」を導入している学校では、普段から学校と保護者との連絡や課題提出などがClassi上で行われ、比較的スムーズにオンラインを活用した学校運営に移行できたという声をいただきました。

しかし、このコロナ禍でのサービス運営は、我々にとってもサービス開始から約6年の間に経験をしたことのない未曾有なものでした。3月以降、特に春休みが明けて1学期が始まった4月・5月には、全国規模で一斉に先生・生徒・保護者が「Classi」を利用されるようになりました。

この頃から、「Classi」の校内グループでの毎朝の連絡、学習記録での振り返りをきっかけとしたコミュニケーション、課題の提出などでの利用が大きく増え、またアンケートを使った体温調査が行われるなど、通常時とは異なる使い方が見られました。我々もリクエストが増えることはもちろん想定していたものの、想定を大きく上回る、ユーザー数で3倍、リクエスト数で7倍となったことで、4月・5月には慢性的につながりづらい状況を引き起こしてしまいました。

f:id:sasata299:20201022115225p:plain
あるサーバのリクエスト数の推移(2月1日〜5月25日)


Datadog等を見ながら負荷となっている箇所の改善を日々進めていましたが、ボトルネックとなっていた箇所を改善すると次のボトルネックが発生するという状況で、少しずつ改善はするものの根本的な解決には至らない、、、本当に苦しい日々が続きました。自分たちのプロダクトにも関わらずコントロールが効かない、まるでコントロールを失った飛行機を操縦しているような、そんな絶望的な感覚でした。

なんとかこの危機的状況を改善したいと、毎日のように社内で対策を検討し、パフォーマンスの改善はもちろん、負荷となっていた部分の機能を一部止めたり、「Classi」本体のサービスとは切り離した代替機能を準備したりと全社一丸となって全力で対応にあたっていました。

6月以降の動き

6月以降は、つながりづらい状況を引き起こす根本的な原因の調査と、その解決に向けて動く社内体制の構築を急ピッチで進めていきました。これを社内では「再建プロジェクト」と呼んでいます。

6月に入ると徐々に休校が解除となり、分散登校や時差通学などで学校への登校が再開され、リクエスト数も落ち着いてきました。安定してご利用いただける状況となったのですが、今後また休校などによってリクエストが大きく増えた際にもサービスを安定してご利用いただけるように、7月以降は「再建プロジェクト」と「セキュリティ強化」を最優先で進めています。

セキュリティ強化では、既報の外部の攻撃者による不正アクセスの再発防止策(4月時点で実施済み)に加え、より安心・安全なサービスを提供できるよう、さらなるセキュリティ強化策について検討、実施しています。4月の不正アクセスと直接関連するものではありませんが、潜在的なリスクを減らすために、フレームワークやライブラリのアップデート、ネットワーク構成の見直しなども進めています。

再建プロジェクトでは、まず現状の分析からはじめ、技術的な課題やつながりづらい状況を引き起こした組織的な課題を一つひとつ明らかにすることからはじめました。その結果、組織的な課題として、チームの責任範囲が明確でないことや、一部のリポジトリのメンテナンス責任が曖昧、といったことが根本的な原因の一つとして浮かび上がってきました。

体制変更

一つのひとつの現象と根本的な原因を丁寧に分析し、その結果に対して、適切に遂行できるよう社内の体制も変えました。

7月からは、動作しているすべてのコードに対して、チームの責任範囲を明確にしました。また、技術的な課題をそれぞれのチームの責任において改善するような動き方にも変えました。やるべきことが明確(「再建プロジェクト」と「セキュリティ強化」が最優先)で、かつ、チームが主体となって意思決定する形にしたことで、現在は各チームが担当する機能やリポジトリをしっかりとメンテナンスしていく、そんな体制になってきたと思います。

その形をより強化するために、また、やるべきことを今後もぶらさないために、「技術的負債を適切にコントロールする」「技術的な意思決定を行う」ことを責務として、10月からは各チームにテックリードを置く形に変えました。各チームのテックリードが目を光らせることで、健全にメンテナンスしていける仕組みを作りたいと思います。

これらは一例ですが、今後も間違いなく課題は出てくるでしょう。いわゆる銀の弾丸はないので、プロダクト同様、組織についても少しでも良い形にできるように改善を続けていきます。

終わりに

「Classi」は、サービスの提供から約6年で、日本中の高校の半分以上、高校生の3人に1人に使っていただく、いわばライフラインとも言えるサービスにまでなりました。今現在も多くの学校、そして家庭で、先生・生徒・保護者の方々にご利用いただいています。

ユーザーの皆さんに、本当に使いたいときに当たり前に使っていただける。今回振り返ってみて、まず我々がやるべきことは、安心・安全なサービスを、安定して提供することだと改めて強く感じています。そのためにも、引き続き、全社一丸となって「再建プロジェクト」と「セキュリティ強化」をしっかりと進めていきます。

新型コロナウイルスの影響で全国の学校が休校になってどうなったか

みなさん、こんにちは。 Classiデータ/AI部でデータサイエンティストをしている、てつろう(@tetsuroito)です。 Classiでもエンジニアやデータサイエンティストの人数も増えてきたことから、テックブログを開設し、積極的に情報を発信していこうという機運が高まり、新たにブログを開設しました。今後の情報発信にご期待ください!

新型コロナウイルスへの対応

今はどこもかしこも新型コロナウイルス関連で大変な時期に差し掛かっていますね。いち早く事態が収束することを願ってやみません。 当社でも1月末ごろからプロジェクトチームが発足し、現在は全社員が原則在宅勤務にて対応するという状況になっています。 詳細は当社のお知らせに記載がありますので、もしご興味があればご覧いただければと思います。

さて、教育業界の対応についてですが、ニュースなどでも盛んに報道された、2020年2月27日に開催された「第15回新型コロナウイルス感染症対策本部」にて、安倍 晋三総理大臣は全国全ての小学校・中学校、高校などに、2020年3月2日から春休みに入るまで臨時休校を要請する考えを表明しました。

それに伴い、当社でも全国の学校向けに一部の機能を無償で提供する対応を実施しています。

参考:新型コロナウイルスの影響で学校活動を休止する全国の高校へ一部サービスを無償提供

これまで当たり前だった学校に行って、学ぶという前提が大きく崩れ、学校へ行かずとも学びを止めないという考え方へ急遽シフトせざるを得なくなりました。実際に多くの学校でClassiを使った様々な対応を現場の先生方がなされており、その対応の臨機応変さに感動しました。

休校対応のClassiへの影響

多くの学校でClassiを活用し、学校の連絡や学習の状況についてのやりとりするという状況が始まりました。Classiでもエンジニア部門がこれまでの想定よりも多くの負荷を捌くために、様々な対応をしていたり、マーケティング部門がこういった場合にどのような使い方をすれば、影響が大きくならないかを考え、それぞれの学校へ密に連携するなど、急場な対応を迫られました。 データ/AI部では、これらのサービスの利用がすぐに可視化できるように、これまで整理してきたデータ基盤を有効活用し、すぐさまダッシュボードを作成しました。 今回はそこから抜粋し、Classiが休校対応でどのように使われていたかを説明します。

ClassiのDAU(Daily Active User)

f:id:tetsuro-ito:20200316215427p:plain
DAU

Classiのユーザーには3種類のタイプがあります。学校の先生が利用するアカウント、学校の生徒が利用するアカウント、そしてその生徒の保護者が利用するアカウントの3種類です。

上記のグラフはそれぞれのタイプの日々の利用推移のグラフです。 まもなく年度末に差し掛かる時期だったので、通常であれば安定的な利用状況になるのですが、臨時休校の要請についての発表が行われた2月末を起点に、生徒や保護者の利用が急増しました。

特に顕著なのは保護者のアクセスのスパイクです。これは休校対応によってどうしたら良いのか、学校との連絡を待つため、Classiへ情報を取りにきたことが考えられます。 先生や生徒は実際に学校で普段からご利用いただいている状況ではあったのですが、それでも休校後はアクセスの平均がこれまでよりも上昇しています。

機能利用の内訳

では、実際に各ユーザータイプごとの機能利用状況を見てみましょう。まずは先生からです。

f:id:tetsuro-ito:20200317163354p:plain
先生

圧倒的に利用されているのは学校内での連絡や情報共有のために使われる校内グループです。様々な情報が錯綜する中で、学校側が生徒や保護者に必要な情報を適切に配信していたのがわかります。

つづいて、学習記録という日々の学習時間などを記録する機能がよく利用されています。これは学校で確保されていた授業の時間という前提が崩れてしまったため、家庭学習の記録を促し、先生から生徒へのコメントなどを行なっているものです。 残りの機能は満遍なく利用されていますが、それぞれの学校のスタイルに合わせて各機能をご利用いただけているようです。

次に生徒の利用状況です。

f:id:tetsuro-ito:20200317163440p:plain
生徒

こちらもやはり校内グループの利用が群を抜いています。先生からの連絡を受け取り、みましたというボタンを押してレスポンスする機能があるのですが、これで出欠を取ったり、日々の健康観察のような使い方をしていたために、多く利用されています。 学習記録は前述した通り、日々の学習時間を記録して、先生に伝えるために利用していただいています。 3番目はアンケートの利用が多くなっています。これは先生から生徒に対してアンケートを配信することで、それらを集計する機能ですが、学校にこられない分、いろいろな設問で日々の状況を確認していることがわかります。 4番目の生徒カルテは生徒のこれまでの成績を一覧できる機能です。模試の結果やこれまでの成績を見返していることがわかります。 5番目のWebテストはClassi上で問題を解きながら学習をできる機能です。こちらもアンケート同様、先生が毎日課題を配信して、その課題に対して各生徒が家庭で学習に取り組んでいることがわかります。

上記のように、学校に行くという前提が崩れてしまっても学びは継続的に行われていることがデータからもわかります。

最後に保護者です。

f:id:tetsuro-ito:20200317163633p:plain
保護者

保護者の機能はシンプルなので、基本的には学校からの連絡を受け取るために多く利用されています。 2番目の生徒カルテはお子さんのこれまでの成績が見られる機能です。タイミングが重なって、自分の子供の成績を確認した保護者の方も多くいらっしゃったのかもしれません。また、アンケートなどを一部の学校で保護者に対して取っている形跡も見られます。

最後に

今回の新型コロナウイルスの流行や全国の学校の休校対応など、これまでの常識では考えられなかった事象が起きています。 従来のICT化される前のままであれば、今回の騒動に対応することは難しかったであろうことは想像に難くありません。 学びの形も少しづつ、着実に進歩しています。今回の件で、多少なりともそれが証明できたのではないかと思います。 Classiは今後も引き続き全社ミッションである 子供の無限の可能性を解き放ち、学びの形を進化させるを実現させるために日々頑張っていきたいと思います。 このような課題に対して前向きに取り組んでくれる仲間をいつでも募集しています!

Classi採用ページへ

© 2020 Classi Corp.