blogger にブログ移行します。
https://blog.moai.tips/ 今後はこちらに移行します。
こちらにあるブログは、少しずつ上記に移していく予定です。 数はそんなにないので、困らない想定です。
ロバストネス分析をやってみるとモードレスっていうのはAPIも単純になっていいなと思った
そもそも
私、設計スキルを学ぼうと思っていて、本とか読んで、インプットだけはやっていたんですが、設計スキルを発揮するにふさわしい複雑な問題には出会ったことがなかったです。 もともと設計というプロセスを挟むことによって、時間はかかるようになるし、いつでもどこでも必要とは思っていないです。 いろいろ本を読んだ今でも、設計プロセスをむやみやたらに挟むのは良くない派です。
とはいえ、設計プロセスの一つである、ロバストネス分析をやってみると問題が単純化するよという例が得られたので、今回記事を書いてみることにしました。
今回考えた問題
こんなUIが与えられているとします。(Google Keepに保存ボタンがついているものです。) このUIでは、何かしらリストを追加したり、リストの内容を変更したりすることができます。
何やら、複雑そうな気がします。
ロバストネス分析をやってみるとどうなる?
これを一度ロバストネス分析をやってみることにします。 以下の図において、赤い丸が、ユーザが行う操作で、緑の丸がブラウザ上におけるエンティティです。 また、グレーの丸がサーバ上のエンティティになります。
複雑度がお分かりいただけたでしょうか?
注目は赤枠で囲われている箇所です。
このUIだと、フロントでサーバ上のリストにある要素か、ない要素かを保存しておく必要があって、実装が複雑になりそうです。 また、APIとしてもRESTだけで表すことはできず、どうしてもリストに削除フラグなどを仕込んで、削除フラグが立っているリストの要素に対してだけ、削除を実施するなどの工夫が必要です。 私はこのロバストネス分析をやってみた結果、この実装はやりたくないなと思いました。
で、どうしたらもっと単純な実装になるUIになるのかなと考えた結果、世の中のリストUIを見ているとリスト全体を含む塊には保存ボタンがついていないことに気づきました。 つまり、モードレスなUIです。
で、モードレスなUIにするとどうなる?
リスト全体を含む塊には保存ボタンがついていない、モードレスなUIというのはつまり、こういうUIです。
こういうUIにするとロバストネス図はこのようになります。
差がお分かりいただけたでしょうか?
フロントで持っていた状態がなくなり、複雑度が一気に減っています。すごい。世の中の人はすごいですね。やはり、周りを見て、どんな実装にしているのかを確認してみるのは大事です。
いろいろやってみてロバストネス分析がどう効いたのか?
今回、ロバストネス分析をやってみましたが、主に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とか業務で使っているとテンプレートファイルを作って、それをコピーして使うようになるじゃないですか。 ただ、そうすると毎回、ファイルを右クリックして、コピーしてファイル名書き換えてとかするわけですよ。 めんどくさい。
具体的に言うと
- こうして(テンプレートファイルを右クリックしてコピーして)
- こうして(右クリックして、ファイル名書き換えて)
- こうする(はぁ、やっとほしいもんが得られた) 作業がめんどくさかったわけです。
そのスクリプト
スクリプトの設置方法
事前手順
Googe App Scriptのアプリは事前に追加しておいてください。
『アプリを追加』から追加する
Google App Scriptを接続しておく
手順
そこのディレクトリに行って、ファイル追加と同じ要領で追加してください。 (そこのディレクトリにいく必要はホントは無いけど、運用的にはそこディレクトリにおいておくのが後々のわかりやすさから親切だと思います。)
スクリプト本体
スクリプト作り方
できたスクリプトファイルの中に、以下をそのままコピペします。 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'; }
スクリプトの動作確認
上の再生マークっぽいものをポチッとすれば、実行できます。
定期実行の設定方法
最後に定期実行を設定しておきましょう。 どうせ毎週やるんだから、忘れないように定期実行させておきましょう。
- すべてのトリガーからトリガー一覧を見つけて
- トリガーを追加して
- よしなに定期実行を設定する
やったね
これで、一つ創造的な時間が増えたぞ。
振る舞いを切り出して共通化するときの指針
なにこれ
振る舞いを切り出して共通化する時、どのようなケースの場合は共通化すればいいんだろうかと悩んできました。 なんとなくこうすれば良いのではと思っていることがあるので書いてみたものです。
背景
完全に同じ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なんかで作っている場合は変更も容易だし、気がついたときに変更すればいいと思います。
ただ、異論も認めます。