コネヒト開発者ブログ

コネヒト開発者ブログ

会議量を見える化する「カレンダー調太郎」を作りました

本記事はコネヒト Advent Calendar 2019の9日目のエントリーになります。

こんにちは!CTOの @itosho です。Jリーグでは横浜F・マリノスが15年ぶりに優勝しましたね⚽おめでとうございます!僕は鹿島アントラーズのサポーターなので、天皇杯に気持ちを切り替えています。

早速ですが、皆さん、会議は好きですか?僕もそうなのですが、しなくていいならしたくないという方が多いのではないでしょうか?というわけで、今日は会議の話をしたいと思います。

カレンダー調太郎とは?

カレンダー調太郎(しらべたろう、と読みます)は開発部*1メンバーの1週間の会議時間を見える化するSlackアプリです。毎週金曜日にみんなの1週間の会議時間をSlackに通知します。また、前週比や来週の見込みも分かるようにしています。

f:id:itosho525:20191208015804p:plain
カレンダー調太郎の通知の一部

こんな感じで通知され、僕の会議時間が21時間だったことが分かります。

何故つくったか?

過去にこんなツイートをしている通り、僕はエンジニアたるものコードを書いてなんぼという考えがあります。特にCTOになってから会議の時間が増え、「これじゃあChief Technology OfficerじゃなくてChief Meeting Officerやで」という課題意識がありました。また、僕以外のメンバーでも時期によって(例えば、目標設定や評価の期間など)、会議の時間が増えることがあります。

ただ「会議が多いな〜」と思っても、それが可視化されていないと危機感が持ち辛く、惰性で過ごしてしまいがちです。そこで定期的に会議量をみんながいる場所で知らせることで改善が進むのでは?と考え、カレンダー調太郎を作りました。

例えば上述の例で言うと、僕の場合週の半分以上の時間が会議に費やされていることが分かります。これは明らかに健全ではないと思うので、減らそうという力学が働きます。実際、見える化してから定例で入っている会議の棚卸しを実施し、頻度や時間を変えるなどのアクションを行いました。

どうやってつくったか?

前提として弊社はG Suiteを利用しているのですが、特別なことはしておらず、スプレッドシートにGoogleカレンダーID(弊社の場合はメールアドレス)とニックネームのリストを作成して、そのリストを元にGASからGoogleカレンダーのAPIを使って、各メンバーの予定を取得して、それをゴニョゴニョして、Slackに投稿しているだけです。シートはこんな感じです。

f:id:itosho525:20191208022314p:plain
スプレッドシートのメンバー管理

GASでやっている処理はコードを読んだ方が早いと思うので、1時間くらいで書いたコードを晒しておきます。NYSL的な感じで自由に使ってください。

// GASの定期実行で呼ばれるメイン関数
function main() {
  var thisMonday = getTargetDay(0);
  var thisSaturday = getTargetDay(5);
  var lastMonday = getTargetDay(-7);
  var lastSaturday = getTargetDay(-2);
  var nextMonday = getTargetDay(7);
  var nextSaturday = getTargetDay(12);
  
  var members = getMembers();
  var message = 'こんにちは〜カレンダー調太郎です!今週のMTG時間をお知らせするよ〜\n';
  message = message + '```\n';
  
  for each(var member in members) {
    var calendar = CalendarApp.getCalendarById(member[0]);
    var thisTotalHours = getTotalHours(calendar, thisMonday, thisSaturday);
    var lastTotalHours = getTotalHours(calendar, lastMonday, lastSaturday);
    var nextTotalHours = getTotalHours(calendar, nextMonday, nextSaturday);
    var diff =  thisTotalHours - lastTotalHours;
    
    message = message + '## ' + member[1] + '\n';
    message = message + '- ' + thisTotalHours + '時間';
    
    var compared = '先週比:';
    if (diff > 0) {
      compared = compared + '+' + diff + '時間';
    } else if (diff < 0) {
      compared = compared + diff + '時間';
    } else {
      compared = compared + '増減なし';
    }
    var forecast = '来週の見込み:' + nextTotalHours + '時間';
    
    message = message + '(' + compared + ' / ' + forecast + ')\n';
  }
  
   message = message + '```\n今週もおつかれさまでした〜';
  
  postSlack(message);
}

// add=0にすると今週の月曜日を返す
function getTargetDay(add) {
  var today = new Date();
  var date = today.getDate();
  var day = today.getDay();
  today.setDate(date - day + 1 + add);
  
  return new Date(Utilities.formatDate(today, "JST", "yyyy/MM/dd")); 
}

function getMembers() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName('foo'); // シート名は適宜変更してください
  var lastRow = sheet.getLastRow();
  var members  = sheet.getRange(2, 1, lastRow - 1, 2).getValues();
  
  return members;
}

function getTotalHours(calendar, from, to) {
  var events = calendar.getEvents(from, to);
  var totalDiff = 0;
  
  for each(var event in events) {
    var startTime = event.getStartTime();
    var endTime = event.getEndTime();
    var diff = endTime - startTime;
    
    var hour = Math.floor(diff / 1000 / 60 / 60);
    // 終日の予定などを除外する
    if (hour >= 8) {
      continue;
    }
    var title = event.getTitle();
    // フレックスの予定を除外する
    if (title.indexOf('フレックス') != -1) {
        continue;
    }
    // 退社の予定を除外する
    if (title.indexOf('退社') != -1) {
        continue;
    }
    // 勉強会の予定を除外する
    if (title.indexOf('勉強会') != -1) {
        continue;
    }
    var guests = event.getGuestList(true); // trueの場合イベントオーナーを含む
    // 参加者が2人未満の場合は除外する
    if (guests.length < 2) {
      continue;
    }
    
    totalDiff = totalDiff + diff;
  }
  
  var totalHours = Math.round(totalDiff / 1000 / 60 / 60);
  
  return totalHours;
}

function postSlack(message) {
  var payload = JSON.stringify({'text': message});
  var options = {'method': 'post', 'contentType': 'application/json', 'payload': payload};
  UrlFetchApp.fetch('https://hooks.slack.com/services/foo', options); // URLは適宜変更してください
}

1点、当たり前ですが「カレンダーに登録されている予定 = 会議」とは限らないので、ご注意ください。コネヒト社ではフレックス制を導入しているのでフレックス退社っぽい予定や勉強会っぽい予定、個人のToDoとして登録してるような予定は除外しています。カレンダーの使い方は人それぞれですので、あくまで相対的な指標としてみるのが良いかもしれません。

まとめ

カレンダー調太郎自体はまだ作ったばかりなので、今後継続的なチューニングが必要ですが、まずは見える化が出来たことは良かったと思います。ただ、見える化と言うからにはきちんと改善のアクションまで持っていかなくてはいけないので、そこはもう少しデザインしていく必要があるかなと思います。例えば、定期で入っている予定があれば「これは本当に必要な会議なのか?」的なリマインドを時々するといった機能を入れるといったことを考えています。

もちろん、会議をすること自体は悪ではありません。しかし、やはりエンジニアはコードで社会に対して価値を生み出していくのが生業だと思うので、会議の量は開発組織の健全性を測る指標の一つになるのではないかと考えています。というわけで、今後もコネヒトでは会議の減らして、生産性を上げていく取り組みを積極的に行っていきたいと思います!

PR

というわけで、弊社では自分の書いたコードで社会を良くしていきたいエンジニアを大大大募集しております! 1mmでも興味がありましたら、下記のWantedlyから「話を聞きに行きたい」ボタンをポチっと押していただくか、僕にDMを雑に送っていただいても構いませんので、まずはカジュアルにお話させていただければと思います!

www.wantedly.com

*1:弊社のエンジニアやデザイナーが所属する部署