moai's blog

ソフトウェア開発にまつわる内容を記載します。

ロバストネス分析をやってみるとモードレスっていうのはAPIも単純になっていいなと思った

そもそも

私、設計スキルを学ぼうと思っていて、本とか読んで、インプットだけはやっていたんですが、設計スキルを発揮するにふさわしい複雑な問題には出会ったことがなかったです。 もともと設計というプロセスを挟むことによって、時間はかかるようになるし、いつでもどこでも必要とは思っていないです。 いろいろ本を読んだ今でも、設計プロセスをむやみやたらに挟むのは良くない派です。

とはいえ、設計プロセスの一つである、ロバストネス分析をやってみると問題が単純化するよという例が得られたので、今回記事を書いてみることにしました。

今回考えた問題

こんなUIが与えられているとします。(Google Keepに保存ボタンがついているものです。) このUIでは、何かしらリストを追加したり、リストの内容を変更したりすることができます。

保存ボタン付きのリスト。リストの一行に保存ボタンがついているのではなく、リストを含むセクション全体に対して、保存ボタンがついている。
保存ボタン付きのリスト。リストの一行に保存ボタンがついているのではなく、リストを含むセクション全体に対して、保存ボタンがついている。

何やら、複雑そうな気がします。

ロバストネス分析をやってみるとどうなる?

これを一度ロバストネス分析をやってみることにします。 以下の図において、赤い丸が、ユーザが行う操作で、緑の丸がブラウザ上におけるエンティティです。 また、グレーの丸がサーバ上のエンティティになります。

f:id:ryamazaki:20180429131346p:plain

複雑度がお分かりいただけたでしょうか?

注目は赤枠で囲われている箇所です。

このUIだと、フロントでサーバ上のリストにある要素か、ない要素かを保存しておく必要があって、実装が複雑になりそうです。 また、APIとしてもRESTだけで表すことはできず、どうしてもリストに削除フラグなどを仕込んで、削除フラグが立っているリストの要素に対してだけ、削除を実施するなどの工夫が必要です。 私はこのロバストネス分析をやってみた結果、この実装はやりたくないなと思いました。

で、どうしたらもっと単純な実装になるUIになるのかなと考えた結果、世の中のリストUIを見ているとリスト全体を含む塊には保存ボタンがついていないことに気づきました。 つまり、モードレスなUIです。

で、モードレスなUIにするとどうなる?

リスト全体を含む塊には保存ボタンがついていない、モードレスなUIというのはつまり、こういうUIです。

f:id:ryamazaki:20180429122817p:plain
リスト全体を含む塊には保存ボタンがついていないリストのUI

こういうUIにするとロバストネス図はこのようになります。

f:id:ryamazaki:20180429130854p:plain

差がお分かりいただけたでしょうか?

フロントで持っていた状態がなくなり、複雑度が一気に減っています。すごい。世の中の人はすごいですね。やはり、周りを見て、どんな実装にしているのかを確認してみるのは大事です。

いろいろやってみてロバストネス分析がどう効いたのか?

今回、ロバストネス分析をやってみましたが、主に2点有効だったかなと思います。

  • 複雑な実装になりそうなことを図で表すことができて、チームでイメージが共有できる。
  • 実装を簡略化したことを図で表すことができて、実装が簡単になったことを評価できる。

一度、やってみるということは、その威力が知れてよかったなと思いました。

ロバストネス分析の参考図書

ロバストネス分析をはじめとするICONIXプロセスについては、下記の書籍が詳しいです。 私もロバストネス分析をはじめとするICONIXプロセスを知ったときは非常に感銘を受けました。エンティティと振る舞いを見出していくパターンのようなものがあるということに対して、非常に意義深い本だと思います。 興味があれば、是非一読ください。

「決断の本質」をおすすめする

moai's blog 現在はこちらで、やっていっています。

 

この記事は、以下の本がとう良かったのかを書いた記事です。

 

 

サマリー

 

  • この本はここ半年で読んだ中でベスト3に入るいい本。
  • 物事が決まらんなーと困っている人にぜひ読んでほしい。
  • こんなことが書いてある
  • よい意思決定とは何か?
  • 意思決定に対するベストプラクティス
  • 意思決定のベストプラクティスの運用


こんな人には役に立つと思う


こんなシチュエーションを抱えている人にとっては役に立つと思います。

 

  • 物事は決まらんことが多いと感じている人。
  • 決まったと思ったら、後で議論は紛糾するなどを何度も経験した人。
  • 決断はいつもやりずらく、どのような順番で、何をどうやって決めていったらいいのかわからない人
  • 意思決定をスムーズに進める本とかないんか?とか思っている人

結構あるシチュエーションだと思います。私も明確な正解がない状況で、決断を下すというのは非常に難しいなと思います。特に決断を下すこと自体は決めの問題ではあります。しかし、それがチームに支持され、その決断に従ってチーム全体が行動する、そんな決断をスピーディーにしっかりと決めていくのってほんとに難しいと思います。

だからこそ、この意思決定の手法が存在すること自体に意義がありますし、事例が明示されてることによって、ある程度の確かさも与えてくれています。

 

どのような場面で役に立つのか?

こんな場面で役に立つと思います。

 

  • 何をどのように決めたらいいのかわからなくなって、決めることができなくなってしまっているとき。
  • いくつかの意見が出て、個々の意見にに誤りはない。ただ、すべてを実行することはできず、どれか一つもしくはいくつかを選べないといけないとき
  • 問題すらよくわからず、不透明な状況で物事を決断していかなくてはならないとき
  • 何かしらの決定案をブラッシュアップするためには案を出して、それを批判して、洗練させていくプロセスを踏めばよいのは、暗に感じている。しかし、それって本当にそうなのか明確に認識していないとき。
  • この本ではそれが有用に作用している例を提示しています。
  • 案を出す、批判するっていうプロセスって、個人的な感情的な対立を産んだりするので、扱いが怖いと思っているとき

それをどのように使っていくのか、いつが使い時なのかが提示されています。
また、建設的に解決するためには、どのようにすればよいのかということについても言及されています。

人が困るのは明確な答えがない状況です。明確な答えがない状況で、いくつか選択肢がある場合、どの選択肢にもある程度、確かさがあり、正しさがあります。だからそもそも選ぶのが難しい。

また、そうした状況では、何かを選ぶためには、その選択肢を選んだ理由や、他の選択肢を外した理由が必要です。

でも、そういうことを考えるのには当然時間が掛かるし、毎回行うわけには行かない。一方でそのステップを怠ると何が起こるか?というと、納得感の欠如から引き起こされる、その決定に対するチームの貢献の欠如です。

だから、このステップを早く、行うことを促進できる方法に意味があります。

 

この本に書いてあること


こんなことが書いてあります。

 

  • この本の立ち位置は、ある組織が行動する前に、こんなプロセスを踏みながら行動までにたどり着くとする。
  • 行動までのプロセス:方針の決定、みんなの貢献が得られる形に合意する、詳細計画の作成、実行
  • このプロセスにおいて、時々で出てくる方針の決定をどのようにするか。そしてその決定の質を高めるためにはどのようなことをすればよいのかに主眼を置いている。
  • また、プラクティスを実践するためにはどのようなことに気を付ければよいのか

僕はこの本を読んで、意思決定をすることに対して、研究されていて、そのプラクティスがあることがわかりました。そもそも、そんなことが研究されているなんて思いもしなかったです。

だから、プラクティス自体が存在してることは驚きだったし、その運用方法についてもいくつか注意点があるのも有意義でした。

 

この本に書いてないこと

 

決めたことをどのように運用して、実行するかについての話については書いてないです。


最終的に価値が出るのって、実行した結果だよね?意思決定を効率化するしても、その実行にうつせないと意味がないんじゃないの?という疑問が頭に浮かんだ人がいると思います。

その指摘についてはもっともです。

確かに意思決定したとしても、それがみんなに支持されず、実際に実行に移せなくては意味がありません。

この本では、行った意思決定をどのように支持される意思決定にするか、そのためにはどのようにすればよいのかということは書いてあります。

ただ、その実行をどのようにやっているかは書いてないです。


その他


この本はコミュニケーション系の本か、チームを作る系の本とかを読み漁っていた時代に見つけた一冊です。

ついでに読んでいた本とかも、普段のコミュニケーションを円滑に進めるコツを書いている本とかもあって、それはそれで非常に勉強になりました。

それらの本も小さなプラクティスの集積でしたが、それらの本を読むに連れて、どんな物事も小さなプラクティスの積み重ねで、良くしてくことができるんだろうなと思うことができました。

また、この本の内容の実践にはコミュニケーション系の本や、チーム作りの本と一緒に読むと、この本の立ち位置や、解釈の仕方がわかってより良い運用に結びつくと思います。

明日の作業時間って、どのくらいか気になるよね?GASで作ってみた。

/*
  翌営業日の作業時間を計算する
*/
function getTomorrowWorkingHours() {
  const ONE_HOUR_MINS = 60;
  
  // 一日のデフォルト労働時間。オレはもっと働くという人は、労働時間を伸ばしましょう。
  const DEFAULT_WORK_MINS = 8 * ONE_HOUR_MINS;

  // 時間計算するときの定数(こんなに宣言する必要はない)
  const SECOND_MILLISECOND = 1000;
  const MINUTE_MILLISECOND = ONE_HOUR_MINS * SECOND_MILLISECOND;
  // const HOUR_MILLISECOND = 60 * MINUTE_MILLISECOND;
  
  var targetDate = getNextWorkingDate(new Date());
  // targetDate.setDate(targetDate.getDate() + 3);
  
  var events = CalendarApp.getDefaultCalendar().getEventsForDay(targetDate);
  Logger.log('翌日のMTGの数: ' + events.length);
  
  var mtg_minuts = 0
  for( var i = 0; i < events.length; ++i ) {
    var event = events[i]
    // 19時以降は営業時間外なのでカウントしていない。
    if(!event.isAllDayEvent() && event.getStartTime().getHours() < 19 && event.getMyStatus() !== CalendarApp.GuestStatus.NO ) {
      mtg_minuts = mtg_minuts + (event.getEndTime().getTime() - event.getStartTime().getTime()) / MINUTE_MILLISECOND ;
      var startTimeStr = Utilities.formatDate(event.getStartTime(), 'JST', 'HH:mm');
      var endTimeStr = Utilities.formatDate(event.getEndTime(), 'JST', 'HH:mm');
      Logger.log('event name: ' + startTimeStr + '-' + endTimeStr + ' ' + event.getTitle() );
    }
  }
  var tomorrowWorkingTimeMsg = '翌営業日のあなたの作業時間: ' + ((DEFAULT_WORK_MINS - mtg_minuts) / ONE_HOUR_MINS ) + '時間';
  Logger.log(tomorrowWorkingTimeMsg);
  // 翌日の作業時間をmailする
  MailApp.sendEmail('your_name@your_domain','翌営業日のあなたの作業時間', tomorrowWorkingTimeMsg)
}


function getNextWorkingDate(date){
  // 休日を除外するため日本の休日カレンダーを取ってくる
  const holidayCalenderOfJapanese = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com');
  
  var targetDate = date;
  
  while(true){
    targetDate.setDate(targetDate.getDate() + 1);
    
    // 土日と休日の場合は、更に翌日を見るようにする
    if (targetDate.getDay() == 0 ||
        targetDate.getDay() == 6 ||
        holidayCalenderOfJapanese.getEventsForDay(targetDate).length > 0){
      targetDate.setDate(targetDate.getDate() + 1);
    }else{
      return targetDate;
    }
  }
}

Gmailを検索してサマリーを自分のメールアドレスに送るスクリプト

なにこれ

Gmailを検索してサマリーを自分のメールアドレスに送るGoogle App Script

スクリプトの事前手順

事前手順とかは、こちらをご覧ください。 blog.ryamazaki.com

スクリプト本体

function engKintaiMailGrep() {
  var yesterday = new Date();
  // ここをよしなな検索条件にすると適当に取ってこれます。
  var threads = GmailApp.search('subject:勤怠 newer:' + getYesterdayDateString());
  // Logger.log(threads.length);
  var offPeopleSubjects = threads.map(function(thread) {
    var firstMessage = thread.getMessages()[0];
    var subject = firstMessage.getSubject();
    // Logger.log(subject);
    var offWordInTitle = subject.match(/休み|やすみ|欠勤|全休/);
    var offWordInBody = firstMessage.getPlainBody().match(/休み|やすみ|欠勤|全休/);
    // Logger.log(offWordInTitle);
    // Logger.log(offWordInBody);
    var is_off = offWordInTitle || offWordInBody
    return is_off ? subject : null
  });
  var compactedOffPeopleSubjects = offPeopleSubjects.filter( function(subject){ return subject } );
  // Logger.log(compactedOffPeopleSubjects);
  var compactedOffPeopleSubjectsJoinsNewline = compactedOffPeopleSubjects.join("\n");
  // Logger.log(compactedOffPeopleSubjectsJoinsNewline)
  // 今回は誰かのメールに通知するために、最後にmailを送る処理を書いてます。
  GmailApp.sendEmail("hogehoge@hogehoge.com", "今日のお休み", compactedOffPeopleSubjectsJoinsNewline);
}


/**
  昨日の日付を取ってくる
**/
function getYesterdayDateString(){
  var today = new Date();
  var yesterday = new Date();
  yesterday.setDate(yesterday.getDate() - 1);
  return yesterday.getFullYear() + '/' + (yesterday.getMonth() + 1) + '/' + yesterday.getDate();
}

定期実行手順

定期実行手順とかも、こちらをご覧ください。 blog.ryamazaki.com

G Suiteで作ったテンプレートファイルを定期的に特定のフォルダにコピーするスクリプト

なにこれ?

タイトルの通りのスクリプトを作ってみる記事。

困っていたこと

ざっくりいうと

G Suiteとか業務で使っているとテンプレートファイルを作って、それをコピーして使うようになるじゃないですか。 ただ、そうすると毎回、ファイルを右クリックして、コピーしてファイル名書き換えてとかするわけですよ。 めんどくさい。

具体的に言うと

  1. こうして(テンプレートファイルを右クリックしてコピーして) f:id:ryamazaki:20170128160538p:plain
  2. こうして(右クリックして、ファイル名書き換えて) f:id:ryamazaki:20170128160551p:plain
  3. こうする(はぁ、やっとほしいもんが得られた) f:id:ryamazaki:20170128160602p:plain 作業がめんどくさかったわけです。

そのスクリプト

スクリプトの設置方法

事前手順

Googe App Scriptのアプリは事前に追加しておいてください。

  1. 『アプリを追加』から追加する f:id:ryamazaki:20170128161504p:plain

  2. Google App Scriptを接続しておく

f:id:ryamazaki:20170128161559p:plain

手順

そこのディレクトリに行って、ファイル追加と同じ要領で追加してください。 (そこのディレクトリにいく必要はホントは無いけど、運用的にはそこディレクトリにおいておくのが後々のわかりやすさから親切だと思います。)

  1. 新規ボタンからスクリプトを選択して、作成 f:id:ryamazaki:20170128162005p:plain
  2. スクリプトファイルが作成される f:id:ryamazaki:20170128162230p:plain

スクリプト本体

スクリプト作り方

できたスクリプトファイルの中に、以下をそのままコピペします。 templateのファイルIDと、フォルダのIDは置き換える必要があるので、注意してください。

function copyFileFromTemplate() {
  // template Docを取ってくる
  //   (参考)DocのfileIdは一番最後のやつ。下の例では、 hogehogehogehogehogehogehoge がDocのfileId
  //         https://docs.google.com/document/d/hogehogehogehogehogehogehoge/edit
  var templateDocFile = DriveApp.getFileById('hogehogehogehogehogehogehoge');
  
  // ファイルをコピーして、フォルダに格納する
  //   (参考)IDは一番最後のやつ。下の例では fugafugafugafugafugafugafuga がID
  //         https://drive.google.com/drive/folders/fugafugafugafugafugafugafuga
  var weeklyNoteFolder = DriveApp.getFolderById('fugafugafugafugafugafugafuga');
  templateDocFile.makeCopy(getWeeklyNoteTitle, weeklyNoteFolder);
}

/*
  weeklyのファイルを作成する
*/
function getWeeklyNoteTitle(){
  var date = new Date();
  var y = date.getFullYear();
  var m = date.getMonth() + 1;
  var d = date.getDate();
  var w = date.getDay();

  m = ('0' + m).slice(-2);
  d = ('0' + d).slice(-2);

  // フォーマット整形済みの文字列を戻り値にする
  return y  + m +  d + ' weekly';
}

スクリプトの動作確認

上の再生マークっぽいものをポチッとすれば、実行できます。 f:id:ryamazaki:20170128162636p:plain

定期実行の設定方法

最後に定期実行を設定しておきましょう。 どうせ毎週やるんだから、忘れないように定期実行させておきましょう。

  1. すべてのトリガーからトリガー一覧を見つけて f:id:ryamazaki:20170128163007p:plain
  2. トリガーを追加して f:id:ryamazaki:20170128163014p:plain
  3. よしなに定期実行を設定する f:id:ryamazaki:20170128163023p:plain

やったね

これで、一つ創造的な時間が増えたぞ。

振る舞いを切り出して共通化するときの指針

なにこれ

振る舞いを切り出して共通化する時、どのようなケースの場合は共通化すればいいんだろうかと悩んできました。 なんとなくこうすれば良いのではと思っていることがあるので書いてみたものです。

背景

完全に同じmethodだけど、2クラスぐらいなら抽象化しないままそのまま取り扱ったほうが別クラスなので別に扱ったほうが後々の変更に便利そう。 なので、別に切り出すほどではないような少し似ているくらいなら放置しようかという気持ちになっている。 しかし、同じものは何回も書きたくないのでDRYの法則は守りたい。 そうやってモヤモヤしていたものを最近はこういう指針でやっていこうと思ったので、書いておくことにした。

こういう方針でやればいいんじゃないか?

これを満たすときには共通化すればいいと思う。

  • 記述している振る舞いが本質的に同じものである。
  • 3つ以上同じ処理を書くことが容易に想像がつく。

記述しているものが本質的に同じもの

これは高確率で抽象化しても良いと思う。例えば、本と野菜にどちらも消費税込みの小売価格を計算するメソッドが有ったら、 抽象化して、親クラスを作るなりすればいいと思う。

class Book

  def price
    100
  end

  def retail_price
    self.price * 1.08
  end

end

class Vegetable

  def price
    100
  end

  def retail_price
    self.price * 1.08
  end

end

でも、たまたま振る舞いは同じだけど、本質的には違うものは抽象化したらアカンと思う。 例えば、小売価格を計算するBookと信託手数料を計算するInvestmentTrustクラスは同じ* 1.08するのかもしれない。 だけど、こいつらは抽象化したらあかん。 下で言うと、retail_price と trust_commisionをまとめるようなものです。

class Book

  def price
    100
  end

  def retail_price
    self.price * 1.08
  end

end

class InvestmentTrust

  def price
    100
  end

  def trust_commission
    self.price * 1.08
  end

end

3つ以上同じ処理を書くことが容易に想像がつく

3つ以上同じ処理を書くことが容易に想像がつく場合も抽象化しても良いと思う。 逆に言うと2つくらいなら放置しておく。 2つくらいなら時期がきたら誰かが抽象化してくれると思うし、2つくらいなら複雑性も上がらないと思うから。

下の例で、かつ物を一般向けに販売するシステムなんかを作っていた場合、その小売価格を計算することなんてたくさんあるだろうから すぐに親クラスを作って抽象化すればいいと思う。

class Book

  def price
    100
  end

  def retail_price
    self.price * 1.08
  end

end

class Vegetable

  def price
    100
  end

  def retail_price
    self.price * 1.08
  end

end

でも、上の例でも、かつ物を卸のシステムを作っていて、でも特別に本と野菜だけは特別に小売する場合なら 少し待ってもいいと思う。 時期がきたら特別扱いしなくても良くなると思う。 Rubyなんかで作っている場合は変更も容易だし、気がついたときに変更すればいいと思います。

ただ、異論も認めます。