段ドリーの奇妙なプロジェクト 第01段

史郎は、「小さなIT会社」という会社に勤めている会社員である。一応、マネージャをやっている。マネージャーだけど、「小さなIT会社」(略してSITC Small IT Company)には社員が史郎ひとりしかいない。

でも、部署はいろいろあって「やってんどー」やら「がんばってんどー」などの不思議な名前の協力会社、という下請け会社というか外注会社とチームを組んでやっている。いや、チームを組んでいるのかさだかではないのだが、なんとかプロジェクトをこなしている。というか、毎回「炎上」している。

そんなところにドリーが新入社員として入ってきた。

性別は不明。年齢不詳。最近の履歴書には写真も載っていないし、性別も書いていないし、誕生日も書いていないことが多い。いわゆる、お台場シティというやつだ。いやダイバーだったか、サイバーだったか、よくわからなけど、そういうやつだ。

しかし、出身地のところに書いてある「ホコタテ星」というのはなんだろうか。まあ、出身地がどこだってかまらないのだけど、「ホコタテ星」というのは何処にあるのだろう。ペンギン村は日本にあるらしいけど、ホコタテ星は聞いたことがない。

「名前は、段ドリー。出身はホコタテ星です!」

目の前のドリーが自己紹介をする。彼女…なのか彼なのか不明だけど、一応彼女っぽく見える。いや、本当の性別はわからないけど。

「ホコタテ星?」

「ホコタテ星です」

「それはどこにあるの?」

「地球ではないことは確かですね」

「いや、日本のどこにあるの?北海道とか?」

「いいえ、地球ではないどこかです」

「・・・」

「禁則事項ですから」

いや、新入社員なのだから、禁則事項とか企業秘密とかいうもんだいではあるまい。

「大丈夫です。会社に通うには問題ないですから」

そう言い切ってしまわれると仕方がない。彼女、というか彼というか、ドリーは「ふんむー」と徳家げな顔でこっちを見ているのだが、まあ、いいか。会社に通えるならば問題ない。

「さて、新人の君の仕事なんだが・・・」

「え?新人?私が?」

「ああ、新人だろう?この会社の新入社員なのだから」

「いや、新人じゃないですよ、ベテランです」

「・・・」

「ベテランですよ。段取りのベテランです」

何を言い出すのかよくわからないが、ドリーはベテランらしい。

「何?何のベテラン?」

「だから、段取りのベテランです」

「段取り?何それ?」

「段取りですよ、ダ・ン・ド・リ。知らないですか?段取り!」

「初耳だなあ・・・」

ダンドリってなんだ?聞いたことがない。なんだそれ。

「プランニングとか、ガントチャートとか、チケット駆動とかそういうやつ?」

「いえ、ダンドリですよダンドリ。段取り八分、電話は二番ってやつですよ」

「何それ?」

よくわからないが、貴重な新入社員だ。まあ、社員は一名しかいないわけだが。これで俺、史郎の仕事も楽になるってもんだ。

「まあ、ダンドリはさておき、新人の君の仕事の説明なんだが」

「ベテランです」

「・・・・」

「ベテランです!」

「まあ、いいや。ベテランの君の仕事ななんだが」

「むふー」

いや、その得意満面の顔はいいから、話を続けさせて欲しい。

「べつの部署で、「やってるどー」というのがあるのだ。いま、プロジェクトが進行中なのでそれのマネジメントを君にやってもらおうと思っている」

「やってるどー、ですか、なかなか楽しいプロジェクトそうですね」

「そうだろう、なんとかやってるどーってことで、やってるんだよ」

「なるほど」

実は、やってるどーのプロジェクトは5名の派遣社員というか協力社員というか、メンバーでやっているのだが、まあ、進捗が悪い、というか、なかなか進まない。あまりにも進まないので、3年もプロジェクトが停滞しているぐらいだ。普通、3年もプロジェクトが終わらなかったら困る状況になるのだが、ともかくやってるどーの雰囲気が強くて、何も言い出せない。とにかく、やってるんだ。プロジェクトをやっている感がすごくて、やっている。だけど、終わらない。

「つまり、そこでわたくしが段取りしろというとですね」

「そう、察しがいいね。マネジメントして欲しいんだ」

「段取りですね」

「ああ、マネジメントなんだよ」

「段取り付けるんですね」

「ああ、ああ、わかった、そのダンドリってやつでいいよ」

「むふー」

言い負かされているような気もしないでもないが、まあいいや。ともかく「やってるどー」から離れられれば万々歳だ。ともかく3年も停滞しているプロジェクトで終わりが見えないのが、なんだかーなというプロジェクトなのだから。

「ところで、やってるどープロジェクトは、どこでやっているんですか」

「こっちだよ」

史郎は、隣に続くドアを開いた。実はやっているどープロジェクトは隣の部屋でやっているのだった。真ん中にテーブルが置いてあって、部屋の壁向きに5人が座っている。キーボードをカタカタを押して、モニタにちらちらと文字が書かれている。たまに画面が映っては、マウスでボタンをぽちぽちしたりしている。なにをやっているのかよくわからないが、ともかく凄いやっている。何かやっている。すごくやっているのだが、3年経っているのにこのプロジェクトが終わらない。どこから資金がでてきるのかわからないが、ともかくやっている感が凄いのだ。やる気満々という感じだ。

壁には一面に付箋が貼ってある。いろいろな記号(クラス図やらシーケンス図)が書いてある。壁一面に書いてあって、もとの壁がどこにあるかわからない位だ。壁が三角なのか四角なのかも良く分からない具合で、バーンダウンチャートと棒グラフが書いてある。たくさん書いてあるのだが、何がどうなっているのか分からない。

「やってますか?」

史郎は傍らのひとりのメンバーに声を掛けた。だれがリーダーなのか名前が何と言うのかわからない。自己紹介をしてもらったのは3年前なんだけど、それ以来名前を聞いたことがない。ともかく忙しそうで、掛けづらいのだ。

「やってますか?」

返事がなさそうなので、もう一度声を掛けてみた。いや、返事が返ってこない死人のようだ、という訳ではなく一心不乱にキーボードをたたいているわけだから、仕事をしているのは確からしい。いや、仕事をしているのかどうかわからないけど。

「やってますよ」

返事が返ってきた。

「やってますよ。ほらたくさんやってます。ここのコードをみてくださいな。こんなにもやってます。いろいろとやっているので、一言では説明しきれないのですが、その概要は壁に貼ってある、あれとこれとそれとあれを組み合わせて、ここの画面に出てくるこれとそれを表示させて、最終的にあれとこれがこうなって、それとそれとを組みあわせ、あっちのほうに出てくるのです」

声を掛けたのは失敗したかもしれないが、まあ、いつものことだ。何を言っているのかさっぱりわからないが、ともかくやっているのは確からしい。寝たりサボったりゲームをしたりしていたら、

「さぼらないでくださいねー」

と声を掛けることもできるのだが、忙しく仕事をしている人には声を掛けづらい。さらに、詳しく説明してくれるし、よくわからないけど何かができあがってくる雰囲気だけはあるので、その圧が強い。いやあ、いいものができてくるんじゃないかという感触はある。感触はあるのだが、3年間できあがらないのは、何故だかよくわからないのだけど。まあ、いいものができあがるのは確かなことだろう。信用第一だ。

「で、進捗具合はどうなのですか?」

「進捗ですか、そう進捗ですね。ええ、進んでいますよ、かなり進んでいます。でも、なかなか難しいところがあるので、あと一歩ということろですね」

「進捗率で言えばどうなんですか?」

「そうですね。99%というとろでしょう。あと一息なんですよ」

それはすごい。進捗率99%だなんて、あともう少しじゃないか。いつ終わるのか分からないけど、進捗率が99%なんだから、あと残りは1%だけだ。残りの1%が仕上がれば、晴れてやってんどープロジェクトも完成というところだ。

「ええと、ドリーさん・・・ドリーさんでいいですか?」

「いいですよ、シュロー」

「・・・」

あの、呼び捨てですか。まあ、いいけど、新人だからいいんだけど。

「ドリーさんは、このやってんどープロジェクトを担当してもらいます」

「段取りですね!」

「ええ、まあ、そうです。ダンドリですね。そのダンドリとかいうのを使って、プロジェクトを完成させて欲しいのです」

「いま、聞いたように、現在進捗率は99%!といういうことは、あと1%で完成というところですよ。もう少しなんです」

「なるほど、あと1%なんですね」

「そうです。あと1%なんです」

「・・・」

ドリーはあたりを見回している。壁いっぱいに張られた資料やらグラフやらクラス図やらに圧倒されているらしい。これぞITプロジェクトつまりはアジャイル開発プロジェクトの真髄といったところだろう。黙々とキーボードをたたくベテランメンバーたちに圧倒されているところだろう。新人とは、あと1%のところなのだから、なんとかなろうだろうし、なんとかならなくてもやってんどープロジェクトのメンバーがなんとかしてくれるだろう。そう、なんとかならなくても、俺のせいではなくなるので丁度いいものだ。史郎は胸をなでおろすのだ。実に順調に進んでいるやってんどープロジェクトなんだが、一抹の不安がよぎるのだ。一抹の不安がもう3年も続いているのだが、よくわからない。進捗率は99%であともうひといきなので、大丈夫なはずなのだが一抹の不安がよぎるんだよなー。なぜだろう。

「わかりました!私の段取り力に任せてください!」

「任せました。お願いします」

で、史郎はやってんどーの部屋から元の部屋に戻ったのだった。

カテゴリー: 段取り | 段ドリーの奇妙なプロジェクト 第01段 はコメントを受け付けていません

「図解即戦力 アジャイル開発の基礎」と PMBOK 第7版との類似点

アジャイル開発に基礎の本には、巻末に参考文献が並んでいます。これらの参考文献は、論文風に付けたかったと同時に、本書だけでは「アジャイル開発」の理解するには足りない、ことを明確にしたかったからです。当然ですが、ソフトウェア開発をするときに1冊の本の内容だけで足りることはありません。単純なプログラム言語を学ぶ本であっても、すべてを網羅している(ストラウストラップ氏の本は例外かもしれないけど)本はないでしょうし、ある程度のピックアップと省略が必要です。その省略された部分をもともとはどの本に書かれていたものなのかとか、書籍に書いているちょっとした専門用語は実はどの本に書かれいるものなのかを根拠を付けておきたかったのです。つまり、単なる私見を述べているわけではなく、どこかに科学的な根拠がある強調です。

巻末に20冊位の参考文献を付けたのですが、最後に「PMBOKガイド 第7版」が入っています。この文献に関しては、「PMBOK」のほうに重点があり「第7版」のほうは、まあ、最新だからという意味で「第7版」にしてあります。

が、改めて「第7版」のところを調べてみると、どうやらそれまでの「第6版」とは大きく違っているようです。・・・というのを執筆後、というか出版した後に知りました。

第6版までは「プロセスベース」だったのが、第7版からは「コンセプトベース」に切り替わったのですね。それ以前にも、アジャイル開発やCCPMが入っていたのは知っていたので、それも含めてアジャイル本の中にも「PMBOK」の単語/用語を入れていたのですが、どうやら第7版はかなり違うようです。まあ、余談ですが、第6版までの知識で PMP をやっていると、第7版からの PMP とは大きく違うので、それの知識は更新されていますよね?とは思うのですが。そこは余談で。

PMBOK 第7版との類似点

アジャイル本では一般的に「ウォーターフォール開発」を悪手として、それから脱却するために「アジャイル開発」を行うという筋書きになっています。なので、PMBOKが提唱するプロセスベースの「ウォーターフォール開発」あるいは「計画駆動」は古いスタイルという形になり、現状の変化に伴わない、というストーリーになります。ただし、私のアジャイル本では、「計画駆動」というスタイル一本に絞って、プランニングを守る方法としての「計画駆動」と、プランから大きくずれていく可能性が高い「アジャイル開発」という形で区別しています。つまり、プロジェクトを運営していく上で、当初のプランからどれだけずれる可能性があるのか?によって、計画駆動とアジャイル開発を選択するのです。

なので、あくまで「プランとのずれ」によって区別されているので、ソフトウェア開発自体はそう変わりません。品質の確保や、リスク管理のやり方、仕様変更への対処、不具合への対処などは、計画駆動もアジャイル開発でもそう変わりません。

そういう意味で、先の PMBOK 本を改めて購入して通読してみたのですが、

  • 守破離
  • アーンドバリュー
  • リーダーシップスタイルの図
  • プロジェクトバッファ
  • リスクの計算の方法

まで、同じものができます。まあ、第6版のPMBOKにも、CCPMやアジャイル開発の手法が組み込まれていたので、似たものが出てくるのはそうなのですが。なるほど、ここまで同じ用語/手法を共有できるのか、というところです。

そんな訳で、1万円するのですが(物理本だと2万円越え)、「PMBOK ガイドライン 第7版」を購入。

蛇足ですが、PMBOK で使われるウォーターフォールを「予測型」というのはちょっと言い過ぎかと。予測モデル自体は CMMI モデルで出てくるものなので、ウォーターフォール開発は「計画中心」、予測はアーンドバリューなどを利用して変化自体を予測する(従来のアジャイル開発とは異なる)というパターンかなと。

カテゴリー: 開発 | 「図解即戦力 アジャイル開発の基礎」と PMBOK 第7版との類似点 はコメントを受け付けていません

「Azure OpenAI Service入門」を発売します

「Azure OpenAI Service入門」日経BP社

https://www.amazon.co.jp/dp/4296080385

もうちょっと早めに出版したかったのですが、諸々大変で(主に第8章のところで)ちっとばかし遅れて4月頭に発売になりました。一応、新人教育などに間に合ったと思うので良しとしてください。

目次

  • 第1章 Azure OpenAI Service の概要
  • 第2章 OpenAI の概要
  • 第3章 Azure OpenAI Studio の活用
  • 第4章 モデルの種類
  • 第5章 具体的なアプリケーション
  • 第6章 パラメーターの詳細
  • 第7章 プログラムから活用
  • 第8章 アプリから活用
  • 第9章 独自データを使う
  • 第10章 他システムとの組み合わせ

サンプルコード

https://github.com/moonmile/openai-samples

内容的には、OpenAI API を Azure を通して使うパターンです。OpenAI API の場合、Python プログラムが多いのですが、この本では全面的に C# で書いています(ちょっとだけ、Python と Node.js があります)。Python の場合、主にコマンドラインと Web アプリが主流となるのですが、C# の場合はデスクトップアプリやスマホアプリ、Azure Fuctions などと組み合わせが可能です。自前のツールから JSON 形式で OpenAI を呼び出してもよいのですが、本書では Azure.AI.OpenAI パッケージを使って通信をしています。

実は、執筆中に Semantic Kernel が発表されてどうしたものかと思っていたのですが、Azure 上のプレイグラウンドである Azure OpenAI Studio のサンプルコードが Azure.AI.OpenAI パッケージを中心として書かれていることと、Azure の各種の機能と組み合わせることも本書の目的のひとつであったので、Azure.AI.OpenAI パッケージを使っています。Azure.AI.OpenAI パッケージ自体がβ版なので、先行きはなんともいえませんが。あと、Azure AI Studio という形で他の AI が使える仕組みもあるのですが、これも OpenAI API に限るために Azure OpenAI Studio のほうを基準に解説しています。最近 OpenAI 以外の生成AI が発表されつつあるので、1年後ぐらいには状況が変わっているかもしれません。

執筆者としては、第5章の具体的なアプリケーションで ChatGPT を使ってアプリをつくる下りを書いているのと、第6章と第7章で具体的にアプリケーションに組み込んでいる(デスクトップ、スマホ、WEBサイト、Azure Functionsなど)ところがポイントです。このあたりは、Python ではなく C# を選んで欲しいというところですね。まあ、Python で GUI も可能ではあるのですが、私自身 Python はあまり得意でないので(というか、サンプルコードぐらいしか動かさないので)。

カテゴリー: 開発 | 「Azure OpenAI Service入門」を発売します はコメントを受け付けていません

「図解即戦力 アジャイル開発の基礎」の補足

技術評論社から「図解即戦力 アジャイル開発の基礎」を発売したので、ぼちぼちと補足記事を書いていきます。

この本は、いわゆる「はじめてのアジャイル開発」ということで開発者とプロジェクトリーダー向けに書いた本です。具体的なプログラミング技術要素は皆無なのですが、特定のプログラム言語やフレームワークに関係なく使えるノウハウ集です。基本は、20年前に発祥した「アジャイル開発憲章」が日本に伝わったところからスタートします。温故知新的に基本を大切にするという精神。で、20年後の最近のシステム開発(特にチケット駆動とDevOps的なシステムリースサイクル)を含めて書き進めてあります。

さて、その中で少し小難しいのが第4章の「EVMを使ったプロジェクト完了時期の予測」の部分で、よく言われる「アジャイル開発では完了時期が予測できない」という誤解を覆します。まあ、漠然とチケット駆動していると完了時期が読めないのは実際そうなのですが、それがアジャイル開発起因という訳ではないという点です。この部分は、計画駆動(ウォーターフォール開発)でも同じで、プロジェクトの初期にスケジュールを立てたとしても、それに沿っているか、あるいはプロジェクト途中で要件が変更になったときとかにも使えます。

EVM(アーンドバリューマネジメント)自体は、もともと建築界隈で使われているものでもあり、昨今では「EVM」で検索すれば結構な解説サイト(コンサルサイト?)が見つかります。細かい用語の定義はコンサルサイトに譲るとして(日本語だと若干揺れがあるので、元の英語で覚えたほうがよさそうです)、グラフとしてはこんな感じです。

書籍の EVM の図は、巷にある EVM にあわせて出来高実績と投入実績の2本の線を書いていますが、実はこの2本の線は同じデータから作られているだけです。なので、実際のプロジェクトではオレンジの出来高実績いわゆるプロジェクトの実績=進捗率と、計画段階の青の完了時予算の2本のグラフを比較すればOKです。

計画段階が青の線、プロジェクト実行時に遅れが出るのでオレンジの線、これを黄色の線のように遅れを取り返すにはどうしたらいいだろうか?という計算です。

書籍では紙面の都合上、遅れを取り戻すときの計算を省略していますが、ここで補足をしておきましょう。

チケット駆動での遅延を予測する

例えば、120枚のチケット(あるいはタスク、あるいはWBS)があるときを想定する。仮に、1日の消化チケット数を3枚として、1月を20日と考えると、60枚/月の計算になる。実際は、チケット/タスク/WBSの計画時間のブレがあったり、作業量の大きさが変わっているところだが、ここでは説明を簡単にするためにチケットの作業量の「粒」をそろえておく(この手法は、書籍に書いてある)

この計算だと、120枚のチケットは、2か月間で終わることになり、このプロジェクトは2人月と言う計画になる。

プロジェクトの進捗状態はバーンダウンチャートやEVMなどを使うことにあんるだろう。

さて、ここでプロジェクト開始から1か月経った状態、つまりプロジェクトが半分まで来た状態を観測する。

  • プロジェクトの実施期間 1か月
  • チケットの実績枚数 40枚

プロジェクトの残り期間は1か月で、残チケット数は 120 – 40 = 80 枚と考えられる。この 80 枚を当初の計画である「60枚/月」で割ると、

  • 80 / 60 = 1.5か月

という計算が良く使われますが、これは間違いです。この「60枚/月」という値は、プロジェクトを開始する前の予測値なので、仮の値でしかありません。プロジェクトが開始して1か月経っているので、既にプロジェクトとしては「40枚/月」という実績がでています。なので、これを使います。

  • 80 / 40 = 2か月

そうなると、残り80枚のチケットを「40枚/月」で割るので、あと2か月程度でプロジェクトが完了することが予測できます。つまり、予定の2か月よりも1か月ほど遅延して3か月のプロジェクトになると予測できます。

プロジェクトによっては1か月の遅延が許容できないことがあります。締め切り日が重要な場合は、プロジェクトを増員します。単純な増員は「人月の神話」に反するところですが、ここでは単純な予測値として、1人月を足すことになるので、1人の増員であればプロジェクトに間に合うのではないかという予測が立つというわけです。

一般的に、計画駆動(ウォーターフォール開発)のほうは締め切りが予測しやすいところですが、これがウォーターフォール開発であってもチケットの消化(ウォーターフォール開発ではWBSなど)が遅延することは同じです。となると、計画駆動開発でもアジャイル開発でもこの EVM の考え方は利用が可能です。むしろ、計画起動で遅延が発生したときに「どのくらい増員すればよいか?」の目安となるでしょう。

計算の肝は、遅延期間を計算するときに、プロジェクト開始の予測値ではなくプロジェクト実施の実績値を使うということです。プロジェクトの計画段階である「60枚/月」という値が楽観的すぎるために遅延が発生しているのすから、それの値を使ってはいけません。実績である「40枚/月」という値を使わないといけません。

チケットの計画時間が統一されないとき

実際のプロジェクトでは、チケット/タスク/WBSの予測時間や実績はさまざまです。できるだけ作業量を揃えたほうがいいのですが、ある程度ブレがあるでしょう。

この場合は、チケット/タスク/WBSの平均値を使います。

計画駆動のWBSの作業量が大きく違う場合は、平均値だと遅延の予測精度が悪いのでもう少し考えないと駄目なのですが、チケット駆動やアジャイル開発で利用するチケット/タスクの作業量が、1時間から1日(8時間)程度の間であれば、まあなんとかなるでしょう。理想的なことを言えば、1チケットは2,3時間程度に揃えておくと、遅延の計算が楽になります。

  • 計画時の総時間 = Σ tplan
  • 実績時の総時間 = Σ tdone

を考えるわけです。当然のことながらプロジェクトで遅延が発生しているときは「計画時の総時間 < 実績時の総時間」となります。

遅延を計算するときには実績値のほうを使うので、

  • tdone ave = Σ tdone / N

のように実績の平均値を使えばOKです。

チケットの増加を予測する

チケット駆動においてはプロジェクトの進行中にチケットが増加するのは必須です。バーンダウンチャートを作るにしても、プロジェクト後半において次々とチケットが増えてしまい、いつプロジェクトが終わるのかが予測がつかなくなってしまうという難点があります。従来型のアジャイル開発の考え方であれば、チケットが増えた分だけ納期を後ろにずらすのが筋ではありますが、状況によっては納期をずらせないこともあるでしょう。また、計画駆動においては納期をずらすことができないので、増員などで対処するしかありません。このとき、どの程度の増員をかけるのか、あるいはリスケジュールをするのかを予測します。

  • 計画時 60枚/月の開発スピード チケット総数 120枚
  • 実施時 40枚/月の開発スピード 前半のチケットが80枚に増加

プロジェクトの前半(かもしれない?)が終わった状態で、この状況であると仮定しましょう。

この状態で、実施されているチケット数が 40 枚。前半の残りが 80 – 40 = 40 枚。後半が 60 枚と考えてみると、プロジェクト後半では 40 + 60 = 100 枚となります。これを実績ベースの 40枚/月で割ると、

  • (40+60) / 40 = 2.5人月

ということで、実際はプロジェクトの残りが 2.5 人月ということが計算できます。が、これは過小評価です。

後半の 60 枚のチケットは、プロジェクト計画時に作成したものなので、プロジェクトが開始する前の予測値でしかありません。既に実績として前半のチケットが 60 枚から 80 枚に増えているという「実績」を得ているので、この増加量をそのまま使いましょう。後半の60枚が80枚に増えると仮定するのが妥当です。このため、前半の残り 40 枚と後半の補正値 80 枚を加算して、

  • (40+80)/40 = 3人月

ということになります。

このような単純化された値の場合、この3人月が後半の値ということが直感的にも分りやすいのですが、何故か実際のプロジェクトでは計画時の 60 枚をそのまま利用してしまい、リスケジュールしたときに過小評価をしてしまいがちです。注意してください。それは思ったよりも遅れているのです。

空白のチケットを活用する

先に書いた通り、バーンダウンチャートを使うとプロジェクト後半のチケットの増加に対応できません。特に終了が予測できない状態になってしまいます。なので、書籍では右肩上がりの EVM で解説を行っています。

しかし、EVM では予測値を出すときにウォータフォール開発のようにあらかじめプロジェクト全体の工数が必要となってきます。しかし、アジャイル開発やチケット駆動のようにプロジェクト実行時にチケット/タスク/WBSが増加することには、そのままでは対処できません。

この場合は、空白のチケットを利用します。時間軸上、プロジェクトが完了すれば全体のチケット数が判明します。そこで、プロジェクトが完了した未来の視点からチケット数を見ておいて、全体のチケット数を仮定します。これは計画駆動のWBSの洗い出しと同じ方法なのですが、WBSのように正確に出す必要はありません。

チケットの「粒」を揃えておけば、空白のチケットを 20 枚用意する、100 枚用意する、ということが可能です。つまり、1枚3時間ほどの空白チケットをあらかじめ用意して積んでおくのです。従来では言えば、プロジェクトの「保険」として使うものでもありますが、「空白のチケット」として明示的に用意することで、チケットの増加具合が空白チケットの減少具合で判明します。さらに、EVM と併用すれば、仮ではありますが総チケット数が判明するので、予測グラフが書きやすくなります。

残念ながら、この「空白のチケット」と EVM を組み合わせたツールは現在のところ販売された形跡はありませんが、数式としても簡単なものなので十分に活用できるでしょう。時間軸の問題や精度の問題、「プロジェクト完了時には分散する確率は0になる」の話はまた後日。

カテゴリー: 開発 | 「図解即戦力 アジャイル開発の基礎」の補足 はコメントを受け付けていません

リファクタリングの価値を概算する

元ネタは、以下にあるリスクの計量です。

https://twitter.com/moonmile/status/1683420756598988800

リファクタリングの価値の考察 – プログラマーの脳みそ https://nagise.hatenablog.jp/entry/2022/08/12/121524

ここにある需要と供給の交点「均衡点」をどのように算出するのか?という話…だと思うのですが、要は、「開発者がリファクタリングしたいと言ったとき、プロジェクトマネージャや顧客をどのように背って説得するのか?」と言う問題です。

開発者にとっては、リファクタリングは価値があるものとして当然な結果なのですが、マネージャーや顧客にとっては「価値がない」ものとして認識されがちです。

  • リファクタリングの間は、進捗が上がらない
  • リファクタリングすると、コードが減る(進捗が悪くなる)
  • リファクタリングをして、コードが良くなったというが、いまのままでよいのではないか?
  • リファクタリングをして、将来の保守性を上げるというが、どれだけの効果があるのか?今のままでよいのではないか?

という返答を貰いがちです。

結果、リファクタリングをするための正式な時間(プロジェクトとして計上される時間)が取れずに、

  • 開発者の個人的な時間を使って、リファクタリングする
  • 開発者が何らかの進捗を上げた後で、隙間時間でリファクタリングする

ということに陥りがちです。これは隠れたコストであり、隠れたリスクでもあります。

リファクタリングに限らず、コードを綺麗に書くことなどの価値は算出できない≒不確実性が多い、というのでそのままになりがちなのですが、ここは「マネージャーや顧客にも責任を担保してもらう」という意味でも、リファクタリング価値を説明可能なものにしておくのがベターでしょう。

という提案ですね。

リファクタリング自身の価値を正確に計測するのが目的ではなく、リファクタリングの価値を可能なものにする(ときには、今回のリファクタリングは諦めたほうがよい)のが最終的な目的です。

リファクタリングの工数が、リスクを上回るとは?

損益分岐点として、以下を想定します。

リファクタリングの工数 < リスクや保守の発生時の費用

ここは異論はないと思います。リファクタリング自身には実際に手を動かすことになるので工数が掛かります。また、未来においては、保守や変更、リスクが発生したときの対処などなどの「費用」が掛かるのは確かです。

この「費用」をマネージャは顧客に説明しやすいように厳密にします。

  • 開発プロジェクトが完了した後に、なんらかの保守作業(画面の変更など)が発生します。
  • 開発プロジェクトが完了した後に、なんらかの外部的要因(法改正など)で変更作業が発生します。
  • 開発プロジェクトが完了した後に、高パフォーマンス要求(ユーザー数の増加など)で性能をアップさせる必要があります。

ここで注意しておきたいのは、開発プロジェクトの瑕疵によりコードを修正する必要がある、というような開発者都合ではないものにしておきます。実際、リファクタリングで重要なのは、不具合を減らすためでもあるのですが、ここではマネージャーや顧客に説明し易いように「開発プロジェクトは瑕疵なく完了している」という状態にしておき、そのあとの追加部分だけを対象にします(それが不可能であっても、可能であると想定します)。

開発プロジェクトは無事終了し、納品が終わっているのですが、DevOps 的に言えば運用保守の段階です。ここで、まったく変更がなければ=売り切りであるのであれば、追加費用としてはゼロです。ゼロの場合には、リファクタリングの余地はありません。開発プロジェクト内の問題としてリファクタリングの工数を捻出しないといけないので、ここでの論法は使えません。諦めてください(とはいえ、開発段階でのリファクタリングは必須であるので、別の方法は後述します)。

開発が終了して、運用保守段階には行った後に、なんらかの追加や変更が発生します。これは、無料で行うのではなくて、なんらかの「費用」が発生します、と定義します。

変更費用を見積もる

そうなると、式を少し書き換えられます。

リファクタリングの工数 < 追加変更数 × 人月単価

「追加変更数」というのは、画面の変更や文言の変更、機能の追加なんどの諸々の数です。正確には sum を使うところですが、ここではざっくりと「追加変更数」でいいです。

変更数は生来的なものであるので、確定的なものではありません。先に書いたように、変更数はゼロかもしれないし、あるいは100かもしれない。バラツキがあり確率的なものです。ここでいう「確率」というのは、プロジェクトが完了して運用に入ったときに、なんらかの変更点(顧客由来の)が加わったときに、どのくらいの確率で変更が発生するのか?ということです。確率は分布します。年間0個かもしれないし、100個かもしれません。インシデントという形で年間10個までという決め方もできる、年間保守費用の中に入っているかもしれません。これは、未来のことなので確定的なものではありません。

確定的ではないのですが、おおまかに予想します。保険会社のように統計的に算出する必要はありません。概算で大丈夫です。概算自体は、開発プロジェクト側ではなくて顧客側が「予算」としてやってくれるものです。

さて、運用されているシステムが、何年使われるかわかりませんが(これも、1年、10年、100年で概算してもよいです)、年間12個の変更点を許容すると考えましょう。だいたい、月1に何かを修正するという考え方です。定額の保守費用を考えるときも、顧客の懐具合を見ながら予算を立てます。また、保守費用を大きく上回る場合は追加費用を貰うのが普通と考えらえます。あくまで見積もりのうちなので。

では、1個の修正に関してどのくらいの予算を掛けるのかを考えます。端的に言えば、どのくらいの人月が掛かるか?ということです。

計算を単純にするために、1個の追加に対して2週間(0.5人月)かかると考えましょう。2人プロジェクトであれば1週間、5人プロジェクトであれば2,3日という具合です。「人月の神話」に反していますが、顧客に対しては人月のほうが通りが良いので、この計算のほうが楽です。

単価を100万円と考えます(多いか少ないかは別として、計算上なので)。

そうすると、年間の変更が12個と予想され(実際に12個あるかどうかは別として、将来的な予想です、1個の変更に対して 0.5人月を見積もり、単価が100万円なのですから、

年間の変更費用 = 12 x 0.5 x 100万円 = 600万円

です。凄い金額ですね。これが、運用5年とかになったら、3000万円とかになって開発費用を大きく上回るかもしれません。

リファクタリングの費用を見積もる

さて、年間の変更費用が 600 万円と仮定しました。そんなに掛かる訳ないだろうという意見もあれば、そんなに掛けられないというのも当然です。

最初の式である、

リファクタリングの工数 < リスクや保守の発生時の費用

に立ちかえれば、リファクタリングの工数が、この 600万円(年間であれば)を下回ればよいのです。

単価100万円という計算をしたので、リファクタリングの工数は

6人月 x 100万円

のように求められます。この場合、リファクタリングによって、年間12個の変更費用がゼロ円になってしまうので、もうちょっと変更が必要です。

リファクタリングをすると、変更時のコストが安くなる(開発者にとって変更が楽になる)という式に変えてみましょう。

リファクタリングありの変更費用 + リファクタリング代金 < リファクタリング無しの変更費用

となればよいので、

リファクタリング代金 < リファクタリング無しの変更費用 – リファクタリングありの変更費用

となるので、リファクタリングの代金は、変更費用分の差額になります。

つまり、後の変更費用(未来の変更費用)が安くなるように、リファクタリング代金を決めればよい訳です。開発者視点としてはここをイコールで結んでもいいのですが、マネージャーや顧客視点として、リファクタリング代金のほうがより低い、という想定にします。

まず、これがリファクタリングをする意味です。このリファクタリングの代金が、変更費用の差分よりも高いのであれば、顧客視点からみてリファクタリングをする意味がありません。

リファクタリングに利用できる時間を算出

リファクタリング費用の最大値が求まったので、これを時間に直します。

リファクタリング費用 = リファクタリング日数 x 開発単価

ここでも計算が簡単になるように人月計算します。開発者の単価(人数や単価)にリファクタリングに掛ける日数を掛ければ、リファクタリングの費用になります。先に、リファクタリング費用の上限を決めたので、この「リファクタリング日数」が、開発時にリファクタリングとして利用できる時間になります。

実際のところは、リファクタリングによって開発効率が上がるという開発期間自体の効果もあるのですが、ここでは開発後の保守費用に対して焦点をあてています。これは、開発時に「何故リファクタリングが必要なのか?」を損益分岐点という形で、マネージャや顧客に説明するための資料として使うためです。

私的な問題でもありますが、個人的に目の前のコードをリファクタリングすべきかどうか(特に、誰かからの引継ぎの場合)にも同じ視点を使います。

  • 開発の効率化の差によっては、リファクタリングをしない
  • 保守性の差によっては、リファクタリングをしない

という選択肢を用意しておきます。多少設計がぐちゃぐちゃであったとしても、今後変更が全くないのであれば、リファクタリングで設計を綺麗にするよりも、コードがきちんと動くことを優先させる、という意味あいです。

逆に、何かリファクタリングっぽいもにのを始めたときに、実際「このリファクタリングが効果的かどうか?」の算出にも使います。開発時間は有限なので、いつまでもテストコードやリファクタリングをしてる訳にはいきません。なんらかの上限が必要になるのです。

最初からリファクタリングの時間を見込む

ここまでの話は、既に開発プロジェクトがスタートしてしまった開発途中にリファクタリングが発生したときを想定しています。ですが、どのプロジェクトもリファクタリングのような作業が発生するのであれば、プロジェクトを計画したときに「リファクタリング時間」を見込んでおいたほうがいいですよね。そのほうが、プロジェクト全体の費用も通しやすくなります。

顧客への説明としては、リファクタリング時間として次のように項目を用意します。

  • IBMが推奨する「ソフトウェアインスペクション」の時間を設ける
  • コード品質を確保するために「ピアレビュー」を定期的に行う
  • スクラムスプリントで最終の金曜日はリファクタリングの時間とする
  • ウォーターフォール方式で、品質確保のため設計見直し、コード見直しの期間を設ける
  • チケット駆動で、リファクタリングのチケットをあらかじめ作っておく

リファクタリングという単語を使っていますが、要は品質管理の一環です。ISO9000 の品質チェックあるは是正処置のひとつでもあるし、「プロジェクト・シン・エヴァンゲリオン」の中にあるリテイクに対するトリアージという仕組みと同じです。リテイクのトリアージのほうは、別途まとめておく予定です。

カテゴリー: 開発 | リファクタリングの価値を概算する はコメントを受け付けていません

Vue2 から Vue3 へ移行する ボタンイベント編

前回の続き

こんな風に CRUD 機能が付いた画面を Vue2 から Vue3 へ移植してみる(実質的には Vue3 から Vue2 形式を書き起こしている)。通常ならば、上部のリスト部分とか、下部の編集項目のところを Vue コンポーネント化するところなのだが、その前段階として、1枚のベタの *.vue ファイルに書き起こす。

template 部分

template>
    <div>
        <h1>Mos Main by vue2</h1>
        <div>
            <ul>
            <li v-for="it in items" :key="it.id">
                    {{ it.id }} : <a @click="onClickItem(it)">{{ it.title }}</a>
                </li> 
            </ul>
        </div>
        <hr />
        <button @click="onClickCreateItem" class="btn btn-primary">新規作成</button>   
        <button @click="onClickUpdateItem" class="btn btn-secondary">更新</button> 
        <button @click="onClickDeleteItem" class="btn btn-danger">削除</button>
        <hr />
        <div>
        <!-- カテゴリ情報を表示 -->
            <div v-if="mode == 0">
                <table class="table">
                    <thead>
                        <tr>
                            <th>項目</th>
                            <th>値</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>id: </td><td>{{  cur.id }}</td>
                        </tr>
                        <tr>
                            <td>title: </td><td>{{  cur.title }}</td>
                        </tr>
                        <tr>
                            <td>image: </td><td>{{  cur.image }}</td>
                        </tr>
                        <tr>
                            <td>作成日時: </td><td>{{  cur.created_at }}</td>
                        </tr>
                        <tr>
                            <td>更新日時: </td><td>{{  cur.updated_at }}</td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <div v-if="mode == 1">
                <div>新規作成のモード</div>
                <table class="table">
                    <thead>
                        <tr>
                            <th>項目</th>
                            <th>値</th>
                        </tr>
                    </thead>
                    <tbody>
                    <tr>
                        <td>title: </td>
                        <td><input v-model="cur.title" placeholder="タイトル" /></td>
                    </tr>
                    <tr>
                        <td>image: </td>
                        <td><input v-model="cur.image" placeholder="画像" /></td>
                    </tr>
                </tbody>
                </table>
                <button @click="onClickCommit" class="btn btn-primary">登録</button> 
                <button @click="onClickClear" class="btn btn-secondary">クリア</button>
            </div>
            <div v-if="mode == 2">
                <div>更新のモード</div>
                <table class="table">
                    <thead>
                        <tr>
                            <th>項目</th>
                            <th>値</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>id: </td>
                            <td>{{  cur.id }}</td>
                        </tr>
                        <tr>
                            <td>title: </td>
                            <td><input v-model="cur.title" placeholder="タイトル" /></td>
                        </tr>
                        <tr>
                            <td>image: </td>
                            <td><input v-model="cur.image" placeholder="画像" /></td>
                        </tr>
                        <tr>
                            <td>作成日時: </td><td>{{  cur.created_at }}</td>
                        </tr>
                        <tr>
                            <td>更新日時: </td><td>{{  cur.updated_at }}</td>
                        </tr>
                    </tbody>
                </table>
                <button @click="onClickCommit" class="btn btn-primary">登録</button> 
                <button @click="onClickClear" class="btn btn-secondary">戻す</button>
            </div>
            <div v-if="mode == 3">
                <div>削除のモード</div>
                <table class="table">
                    <thead>
                        <tr>
                            <th>項目</th>
                            <th>値</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>id: </td><td>{{  cur.id }}</td>
                        </tr>
                        <tr>
                            <td>title: </td><td>{{  cur.title }}</td>
                        </tr>
                        <tr>
                            <td>image: </td><td>{{  cur.image }}</td>
                        </tr>
                        <tr>
                            <td>作成日時: </td><td>{{  cur.created_at }}</td>
                        </tr>
                        <tr>
                            <td>更新日時: </td><td>{{  cur.updated_at }}</td>
                        </tr>
                    </tbody>
                </table>
                <button @click="onClickDelete" class="btn btn-danger">削除する</button> 
            </div>
        </div>
    </div>
</template>

リストの部分は共通にしておいて、CURD 機能を v-if で切り分ける。初心者っぽいけど、新人教育では初心者なのでこれで問題なし。最初は v-if で区切っておいて、徐々に Vue コンポーネントに慣れていくというスタイルにする。いきなり Atomic Design に進むのもアリなんだろうけど、歴史的な経緯を追っておくほうが体系的で覚えやすい。なによりも、業務システムが Vue2 と Vue3 が混在している状態なので両方覚えるのは必須なのだから。

            <div v-if="mode == 2">
                <div>更新のモード</div>
                <table class="table">
                    <thead>
                        <tr>
                            <th>項目</th>
                            <th>値</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>id: </td>
                            <td>{{  cur.id }}</td>
                        </tr>
                        <tr>
                            <td>title: </td>
                            <td><input v-model="cur.title" placeholder="タイトル" /></td>
                        </tr>
                        <tr>
                            <td>image: </td>
                            <td><input v-model="cur.image" placeholder="画像" /></td>
                        </tr>
                        <tr>
                            <td>作成日時: </td><td>{{  cur.created_at }}</td>
                        </tr>
                        <tr>
                            <td>更新日時: </td><td>{{  cur.updated_at }}</td>
                        </tr>
                    </tbody>
                </table>
                <button @click="onClickCommit" class="btn btn-primary">登録</button> 
                <button @click="onClickClear" class="btn btn-secondary">戻す</button>
            </div>

更新するときの画面は

  • テキスト入力(input)を v-model で参照させる(双方向なので)
  • ボタン(button)のイベントは @click で渡す

というノーマルな形にする。:value を使うとかトリッキーな方法もあるのだが、ここはノーマルに v-model を使ったほうが移植性がよい。

vue2 では v-if に即値を使ってしまっているが、vue3 では enum が使える

        <div v-if="mode == MODE.UPDATE">
            <div>更新のモード</div>
            <table class="table">
                <thead>
                    <tr>
                        <th>項目</th>
                        <th>値</th>
                    </tr>
                </thead>

vue2 でも const すれば定義値を使うこともできるけど、1画面内ならば即値でも十分なのでこの形式にしておく。

vue2 の script コード

import axios from 'axios'

export default {
  name: 'MosMain',
  props: {
  },
  data() {
    return {
        items: [],  // カテゴリ一覧
        cur: {},    // 選択したカテゴリ
        mode: 0,    // 表示モード
    }
  },
  mounted() {
    axios.get('http://localhost:8000/api/category')
    .then ( response => {
        console.log( response )
        this.items = response.data
    })
  },
  methods: {
    getCategoryById( id ) 
    {
        axios.get(`http://localhost:8000/api/category/${id}`)
        .then ( response => {
            console.log( response )
            this.cur = response.data
        })
    },
    // 新規作成のAPIを呼ぶ
    createCategory( item ) {
        console.log( "called createCategory" )
        // TODO: POST を記述する
        return 
    },
    // 更新のAPIを呼ぶ
    updateCategory( item ) {
        console.log( "called updateCategory" )
        // TODO: PUT を記述する
        return 
    },
    // 選択項目を新規作成する
    onClickCreateItem() {
        console.log( "onClickCreateItem " + this.cur.title )
        this.mode = 1
        this.cur.id = 0
        this.cur.title = ""
        this.cur.image = ""
    },
    // 登録ボタンを押下
    onClickCommit() {
        console.log( "onClickCommit " + this.cur.title )
        console.log( "onClickCommit " + this.cur.image )
        if ( this.mode == 1 ) {
            // 新規作成の場合 POST を呼ぶ
            createCategory( this.cur )
        } else {
            // 更新の場合 PUT を呼ぶ
            updateCategory( this.cur )
        }
    },
    // クリアボタンを押下
    onClickClear() {
        console.log( "onClickClear ")
        if ( this.mode == 1 ) {
            // 新規作成の場合
            this.cur.title = ""
            this.cur.image = ""
        } else {
            const original = this.items.find( x => x.id == this.cur.id )
            if ( original ) {
                // 更新の場合
                this.cur.title = original.title
                this.cur.image = original.image
            }
        }
    },
    // 
    onClickDelete() {
        console.log( "onClickDelete ")
        // 削除 API を呼び出す
        deleteCategory( this.cur )
    },

    // リストで項目を選択
    onClickItem( item )
    {
        console.log( "onClickItem " + item.title )
        this.getCategoryById( item.id )
    },  
  
    /**
     * 選択項目を更新する
     */
    onClickUpdateItem() {
        if ( !this.cur.id ) {
            console.log( "ERROR: onClickUpdateItem cur.value.id is null" )
            return 
        }
        console.log( "onClickUpdateItem " + this.cur.title )
        this.mode = 2
    },

    /**
     * 選択項目を削除する
     */
    onClickDeleteItem() {
        if ( !this.cur.id ) {
            console.log( "ERROR: onClickDeleteItem cur.value.id is null" )
            return 
        }
        console.log( "onClickDeleteItem " + this.cur.title )
        this.mode = 3
    },
  }
}

その壱と同じように、

  • export default で公開する
  • props, data, mounted, methods ブロックで区切る
  • ボタンのイベントとボタンから呼び出す関数は、methods 内に突っ込んである

data を参照するのに、this.* を付けないといけない。「付けないといけない」と書いたが、C++ や C#, Java などを使っていると、クラス内でのフィールド変数は this.* を付けたほうが解りやすいので、こういう風に書くことが多い。最近の Visual Studio 2022 では this なしが推奨されてるけども、this を付けるほうが外部なのか内部なのかわかりやすいのと、this.* としたところでインテリセンスが効くので、便利だったりする。ただし、最近の傾向としては、できるだけ外部の変数を参照しない=クラス内あるいは関数内だけでおさまるようにする、関数型っぽい書き方が推奨されているので、this と打つのは「面倒くさい」というのはそうなのだろう。F# の let mut と同じように、変更可能な変数の書き方のほうが *面倒くさい* ほうが、心理的に楽に流れる(これは五行大儀の前文にも掛かれている、というネタを披露しておく)ので行動経済学的にも理にかなっている。

よって、vue2 のコードでは、this.items や this.cur が頻発する。どうせローカルでしか参照しない方針ならば this は不要であろうという思想が vue3 にはある。

vue3 の script

では、同じコードを vue3 形式で書いてみよう

import { ref, onMounted } from 'vue'
import axios from 'axios'

/**
 * カテゴリのクラス
 */
class Category {
    id: number
    title: string
    category: string
    image: string
    created_at: string | null
    updated_at: string | null
    is_delete: boolean
}

// カテゴリ一覧
const items = ref([] as Category[])
// 選択したカテゴリ
const cur = ref({} as Category)
// 表示モード(列挙型)
enum MODE {
    DETAIL,     // 詳細
    NEW,        // 新規作成
    UPDATE,     // 更新
    DELETE,     // 削除
}
const mode = ref(MODE.DETAIL)



/**
 * カテゴリ一覧を取得する
 */
function getCategories() {
    axios.get('http://localhost:8000/api/category')
    .then ( response => {
        console.log( response )
        items.value = response.data
    })
}
/**
 * ひとつのカテゴリを取得する
 */
 function getCategoryById( id: number  ) {
    axios.get(`http://localhost:8000/api/category/${id}`)
    .then ( response => {
        console.log( response )
        cur.value = response.data
    })
}

/**
 * 新規作成のAPIを呼ぶ
 * @param item 更新する Category 
 */
function createCategory( item: Category ) {
    console.log( "called createCategory" )
    // TODO: POST を記述する
    return 
}

/**
 * 更新のAPIを呼ぶ
 * @param item 更新する Category 
 */
function updateCategory( item: Category ) {
    console.log( "called updateCategory" )
    // TODO: PUT を記述する
    return 
}


/**
 * 削除のAPIを呼ぶ
 * @param item 削除する Category 
 */
 function deleteCategory( item: Category ) {
    console.log( "called deleteCategory" )
    // TODO: DELETE を記述する
    return 
}

/**
 * ひとつのカテゴリを表示する
 * @param item 選択したカテゴリ
 */
function onClickItem( item : Category) {
    console.log( "onClickItem " + item.title )
    mode.value = MODE.DETAIL
    getCategoryById( item.id )
}

/**
 * 選択項目を新規作成する
 */
function onClickCreateItem() {
    console.log( "onClickCreateItem " + cur.value.title )
    mode.value = MODE.NEW
    cur.value.id = 0
    cur.value.title = ""
    cur.value.image = ""
}

/**
 * 登録ボタンを押下
 */
function onClickCommit() {
    console.log( "onClickCommit " + cur.value.title )
    console.log( "onClickCommit " + cur.value.image )
    if ( mode.value == MODE.NEW ) {
        // 新規作成の場合 POST を呼ぶ
        createCategory( cur.value )
    } else {
        // 更新の場合 PUT を呼ぶ
        updateCategory( cur.value )
    }
}

/**
 * クリアボタンを押下
 */
 function onClickClear() {
    console.log( "onClickClear ")
    if ( mode.value == MODE.NEW ) {
        // 新規作成の場合
        cur.value.title = ""
        cur.value.image = ""
    } else {
        const original = items.value.find( x => x.id == cur.value.id )
        if ( original ) {
            // 更新の場合
            cur.value.title = original.title
            cur.value.image = original.image
        }
    }
}

/**
 * 削除するボタンを押下
 */
function onClickDelete() {
    console.log( "onClickDelete ")
    // 削除 API を呼び出す
    deleteCategory( cur.value )
}

/**
 * 選択項目を更新する
 */
function onClickUpdateItem() {
    if ( !cur.value.id ) {
        console.log( "ERROR: onClickUpdateItem cur.value.id is null" )
        return 
    }
    console.log( "onClickUpdateItem " + cur.value.title )
    mode.value = MODE.UPDATE
    
}

/**
 * 選択項目を削除する
 */
function onClickDeleteItem() {
    if ( !cur.value.id ) {
        console.log( "ERROR: onClickDeleteItem cur.value.id is null" )
        return 
    }
    console.log( "onClickDeleteItem " + cur.value.title )
    mode.value = MODE.DELETE

    /*
    const result = confirm("削除してよろしいですか?")
    if ( result == true ) {
        deleteCategory( cur.value )
    }
    */
}

// ページ表示時に、カテゴリ一覧を取得する
onMounted(()=>{
    getCategories() 
})

コードは TypeScript なので、Category というクラスを定義しているのと、 MODE 列挙子を定義しているのと違いはあるが、同じ動作にしてある。

画面を表示するときのモードは リアクティブを使う

const mode = ref(MODE.DETAIL)

リアクティブという用語がでてくるが、実は vue2 ではうまく隠蔽化されていたものが、vue3 ではむき出しになってしまっただけである。理由は、むき出しにしたほうがステートありとステート無しの画面が意図的に区別ができて画面の表示スピードが上がる、のだが、vue2 ユーザーが vue3 に移行するときに躓くのはここが大きいだろう。

/**
 * クリアボタンを押下
 */
 function onClickClear() {
    console.log( "onClickClear ")
    if ( mode.value == MODE.NEW ) {
        // 新規作成の場合
        cur.value.title = ""
        cur.value.image = ""
    } else {
        const original = items.value.find( x => x.id == cur.value.id )
        if ( original ) {
            // 更新の場合
            cur.value.title = original.title
            cur.value.image = original.image
        }
    }
}

クリアボタンを押下したときに、編集画面を空白あるいは戻す処理を記述したものである。

画面のモードとして新規作成と更新がある訳だが、この部分は別々の関数にわけてもよい。実際に template 上では、新規作成では「クリア」、更新では「戻す」になっていて、ちょっと混乱した形になっている。これは新人研修用に少し混乱させることが目的でもある(実際の業務では、このようにモードで切り替えることが多いので)ので、これで良しとする。

vue2 では this.cur や this.items で参照されていたものが、vue3 では cur.value.* や items.value.* になっている。vlaue が付いているのはリアクティブ(変更通知が可能)となっているので、変更通知が必要のない固定値の場合には、cur.* や items.* と書くことができる。vue3 の本で、このあたりの違いが掛かれているのが無いのが残念なところなのだが、まあ、初学な人には「vue3 ではリアクティブ ref や reactive を使います!」としたほうが迷いが少ないので、そっちのほうがいいだろう。慣れてくるとスピードを優先させて、ref なしと ref ありを区別するとよい。

お次は、編集画面を vue コンポーネント化する

さて、いよいよ vue コンポーネントを使った形に書き換えていく。

vue2 で props と $emits を使って親子のコンポーネント間でデータをやり取りしたものを、vue3 ではどういう風に書くのかということになる。まだ Atomic Design のほうまで至らないが(私的には至らなくても良いと思っている、その理由は別途書こう)。MVVM パターンとか、Flux とかを混ぜると、その「危険度」がわかると思う。

カテゴリー: 開発 | Vue2 から Vue3 へ移行する ボタンイベント編 はコメントを受け付けていません

Vue2 から Vue3 へ移行するための其の壱

「其の壱」とは書いたものの、其の弐があるかわからないが、ひとまず Vue2 から Vue3 へ移行するときの肝を示しておく。

Vue2 から Vue3 の大きな変更点は、Options API から Composition API によって関数の使い方が変更になったということなのだが、あいにく Vue2 の経験の浅い私としては、どちらでも構わない。2年程前から Vue.js を教えていたときは Vue2 であって、去年の途中から Vue3 が盛り上がってきて、今年に至っては Vue3 を使わざるを得ない≒教えざるを得ない状況になっている。

新人にとっては、Vue2 だろうと、Vue3 だろうと関係ないのだが、いざ配属された後では既存の Vue2 を修正する機会が出てくるだろう。あるいは Vue2 から Vue3 に変更するしないといけないかもしれない。さらに言えば、現状では Vue3 の情報(特に日本語の情報)が少なく、インターネットで検索する限り Vue2 の情報がでてきてしまう。古めの Vue2 の情報に引っ掛からないようにするためには、Vue2 の書き方から Vue3 へ自らが書き換えられるようにならないといけない。

Vue3 の参考書

既に、Vue3 の参考書はいくつかでいるのだが、どうもしっくりこなかった。

やむなく、「基礎から学ぶ Vue.js」 https://www.amazon.co.jp/dp/B08JYTSY4T を使っていたのだが、つい5月に技術評論社から「Vue.jsポケットリファレンス」 https://www.amazon.co.jp/dp/B0C26WFHFX が出版されたので急遽これを使っている。

「~ポケットリファレンス」のほうは、Vue3.js を網羅している。今回は、通常のポケットリファレンスとは違って、テクニックの部分よりも基本的なところに紙面を割いている。この部分がちょうど新人研修に向いている。

移行する画面

テスト的に、現在新人研修で使おうとしている画面を Vue2 から Vue3 に移行してみよう。実際のところは、既に Vue3 で作った画面を Vue2 形式に移植しているので、Vue2 特有の関数の使い方になっていないかもしれないが、書き方のスタイルとしては、3年ほど前に私が業務で使った書き方に揃えてある。

画面にリストが表示されて、項目をクリックすると詳細が表示される。一覧や詳細はいちいち Web API を呼び出すようにしてある。

本来ならばコンポーネント化して props と $emit を使ったほうがいいのだが、ここでは Vue2 から Vue3 移行への雰囲気を味わうために省いている。後日、 props と $emit を使ったパターンはやってみようと思う。

template

実は Vue2 でも Vue3 でも template は全く同じものが使える。移行前の Vue2 でどれだけトリッキーなことをやっているかという度合いにもよるのだが、素直な画面であれば変更は必要はない。とはいえ、実際のところはアニメーションやキーイベントなどを細かい操作が入ってい来るから、Vue3 に変更になった時に変更されたイベントには対応しないといけないだろう。

<template>
    <div>
        <h1>Mos Main by vue2</h1>
        <div>
            <ul>
            <li v-for="it in items" :key="it.id">
                    {{ it.id }} : <a @click="onClickItem(it)">{{ it.title }}</a>
                </li> 
            </ul>
        </div>
        <hr />
        <div>
            <table class="table">
                <tr>
                    <td>id: </td><td>{{  cur.id }}</td>
                </tr>
                <tr>
                    <td>title: </td><td>{{  cur.title }}</td>
                </tr>
                <tr>
                    <td>image: </td><td>{{  cur.image }}</td>
                </tr>
                <tr>
                    <td>作成日時: </td><td>{{  cur.created_at }}</td>
                </tr>
                <tr>
                    <td>更新日時: </td><td>{{  cur.updated_at }}</td>
                </tr>
            </table>
        </div>
    </div>
</template>

template からは項目をクリックしたときに onClickItem メソッドを呼び出して、下のほうにある cur.id などが変更されるようにする。items がコレクションで、cur がカレントアイテムということになる。

Vue2 のコード

Vue2 で書くとこんな風になる。MosMain.vue というファイルの script となる。

import axios from 'axios'

export default {
  name: 'MosMain',
  props: {
  },
  data() {
    return {
        items: [],  // カテゴリ一覧
        cur: {},    // 選択したカテゴリ
    }
  },
  mounted() {
    axios.get('http://localhost:8000/api/category')
    .then ( response => {
        console.log( response )
        this.items = response.data
    })
  },
  methods: {
    getCategoryById( id ) 
    {
        axios.get(`http://localhost:8000/api/category/${id}`)
        .then ( response => {
            console.log( response )
            this.cur = response.data
        })
    },
    onClickItem( item )
    {
        console.log( "onClickItem " + item.title )
        this.getCategoryById( item.id )
    },  
  }
}
  • items や cur は、data の return にする
  • 画面を表示するときに mounted 中で記述する。リストを this.items にいれておく
  • template からのイベントを methods の中に記述する。methods の中の関数を呼び出すときは this を付ける
  • 全体を export default { … } で囲って、公開しておく。これを App.vue から使う。

書き方に慣れれば、 props, data, mounted, methods の中に順序よく関数やデータを書くことになる。算出プロパティ(computed)とか監視プロパティ(watch)も同じように分類される。

が、慣れればこの書き方でうまくいくのだが、最初のうちは無理矢理型にはめられている気分であまりよくない。後で解るのだが、それぞれのブロックは、vue.js からのフックするときの情報となっているので、Java の EJB みたいな感じになっている。

Vue3 のコード

この Vue2 のコードを Vue3 に書き換えてみる(実際は、Vue3 から Vue2 を書き起こしている)。

コード的には TypeScript で書いているので、型を指定するために Category クラスを作っているが、概ね Vue2 のコードと機能的には変わらない。

import { ref, onMounted } from 'vue'
import axios from 'axios'

/**
 * カテゴリのクラス
 */
class Category {
    id: number
    title: string
    category: string
    image: string
    created_at: string | null
    updated_at: string | null
    is_delete: boolean
}

// カテゴリ一覧
const items = ref([] as Category[])
// 選択したカテゴリ
const cur = ref({} as Category)


/**
 * カテゴリ一覧を取得する
 */
function getCategories() {
    axios.get('http://localhost:8000/api/category')
    .then ( response => {
        console.log( response )
        items.value = response.data
    })
}
/**
 * ひとつのカテゴリを取得する
 */
 function getCategoryById( id: number  ) {
    axios.get(`http://localhost:8000/api/category/${id}`)
    .then ( response => {
        console.log( response )
        cur.value = response.data
    })
}

/**
 * ひとつのカテゴリを表示する
 * @param item 選択したカテゴリ
 */
function onClickItem( item : Category) {
    console.log( "onClickItem " + item.title )
    getCategoryById( item.id )
}


// ページ表示時に、カテゴリ一覧を取得する
onMounted(()=>{
    getCategories() 
})
  • 画面へ表示する場合は、ref か reactive を使う
  • 呼び出す関数は function で記述しておく。
  • 画面から呼び出される onClickItem も function で記述しておく
  • 画面の初期表示では onMouted にラムダ式を渡す

Vue2 の書き方で items, cur のところが、items.value, cur.value を使うところが一番分かりづらいと思う。リアクティブという仕組みを使って画面に値変更を通知するのだが、実は Vue2 では items, cur が仮想 DOM を使って通知されていたものが、Vue3 になってリアクティブという形でむき出しになってしまった形のため、こうなっている。

リアクティブ自体は、C# では MVVM パターン、Rx という形で10年以上前から使われているものなので、C# や Java 界隈では知っていれば知っているというパターンなんだけど、Vue3 で出て来たので混乱するというパターンだろう。ちなみに「~ポケットリファレンス」では、MVVM パターンと一緒に解説が付けられている。

見た目上、items.value のほうに真の値が入っていて、items には getter/setter で来るんである、と考えればよい。ちょうど算出/監視プロパティの両方が同時に入っているイメージだ。

何故、Vue3 にこのリアクティブの部分がむき出しになったのかと考えるに、Fultter の Stateありと Stateless のコンポーネントにちなんでではないかと思う。Fullter の場合、画面内の変更がない場合は Stateless で作ることが多い。画面内で変更がないので表示が早いためだ。この部分を「関数型の画面表示」ということもあるが、つまりは、State(状態)を持つと仮想 DOM の扱いで負担が大きいのだろう。なので、画像や説明を表示するだけのコンポーネントであれば、ref や reactive を使わずに、状態変更なしで軽い形で表示できる(のではないか?)のが Vue3 の特徴と類推される。

あと、Vue 3.0 の頃では setup に色々と詰め込まなければ駄目だったが(これはかなり非道い仕様だと思う)、Vue 3.1 からは scrupt setup を使うことで、上記のような素直なコードが書ける。というか、setup の中身がそのまま書かれただけなのだが、読みやすのは確かである。

Vue2 の mounted のブロックが、Vue3 では onMounted となっている。算出/監視プロパティの書き方も同じようにラムダ式を渡す形になる。つまり、Vue2 はあらかじめ mounted というブロックに強制的に書かなければいけなかったが、Vue3 ではコードを書く側で onMounted を呼び出して能動的に登録することができる。データ設定の向きの違いであるけれども、私としては Vue3 のほうが、縛られない感じがして気分がいい。まあ、気分の問題だけど。

関数の部分だが、次のようにラムダ式を使っても書ける。

const onClickItem = ( item : Category ) => {
    console.log( "onClickItem " + item.title )
    getCategoryById( item.id )
}

私としては、変数や定数は const、関数は function にしたほうが検索性が高いと思うのだが、このように const で揃えてある実例も多い。「~ポケットリファレンス」この const で統一されている。

Vue2 と Vue3 のコーディングスタイルの違い

Vue2 のがちがちにフォーマットされたスタイルから、Vue3 のちょっと自由に書けるスタイルになったわけだが、C# のコンポーネント(クラス)の場合には、MVVM パターンとイベント登録をすればよいわけで、Vue3 の書き方がのほうが、素直にクラス設計が出来そうだがどうだろうか?ルールの部分が少なくて済むという利点がある。

参考先

Vue3の衰退を招いたのはとCompositionAPIかもしれない という考察 – Qiita https://qiita.com/fruitriin/items/81691ce68cf3678f3bda

カテゴリー: 開発 | Vue2 から Vue3 へ移行するための其の壱 はコメントを受け付けていません

Good Code 書籍の例外処理とフェールセーフ思想の違い

新人教育に「Good Code ~」を使うのだけど、第4章の「エラー」の部分だけは、ちょっと私の思想とは異なる。一般的に言えばそういう書き方が推奨されるのだが、実務的にはフェールセーフな動きが求められることが多い。ゆえに、ある程度のコードを書かずに「Good Code ~」を先に読んでしまうと変なところに陥る(たびたび原著が抜けてている点も含めて)の難点・・・なのだけど、まあ、新人教育には便利な教材である。

例外を呼び出し元に通知するべきか?

15年前位に、アプリケーション例外とシステム例外の区別をすべきかどうか?というのが流行った時期がある。システム例外はオペレーションシステムが発生する例外、アプリケーション例外はシステム以外全般のユーザーが使うという意味でのアプリケーション層ということになる。

一般的にユーザーが使うアプリケーションは、何か問題が起こったときにはユーザーに対して「予期せぬエラーが起こりました!」でアプリケーションが落ちる。「予期せぬエラー」というのは、アプリケーションにとって予期せぬエラーであって、回復不能なのでエラーダイアログには「OK」ボタンしかない。ユーザーには、何らかのエラー番号が伝えられることもあれば伝えられないこともない。予期しないのは、ユーザーにとって予期しない動作だったかもしれないが、アプリケーションを作った開発者にとっても予期しないエラーだったのかもしれない。ともかく、エラーメッセージを出して落ちる。

この「予期せぬエラー」は、たいていの場合はシステム例外が起因となる。アプリケーション内のロジック的なエラーではなく、システム側でHDDが書き込めなくなった、LANケーブルが抜けた、CPUが熱暴走した、という際のハードエラーも含め、オペレーションシステム開発者が出す例外も含める。よくあるのは、システム側がヌルポインターを返す場合もこれにあたる。これの場合、アプリケーションが対処できるかといえば、できる場合もあるしできない場合もある。回復可能かと言えば、再試行できる場合もあれば(Wi-Fiの接続がわるいとか、輻輳とか)、回復できない場合もある。そんなアプリケーション開発者としてはレアなケースでもあり、ユーザーにとってもレアなケースである。

一方で、アプリケーション例外は、アプリケーションの中で何らかのロジック的なエラーが発生した場合と言える。なので、完全なエラーのないプログラムであれば、アプリケーション例外は一切発生しない!のかもしれないし、別の意味での「例外」を発生させる場合もできる。「Good Code ~」の中でいればユーザーが入力する電話番号にアルファベットが紛れ込んだときの場合にもアプリケーション例外は使える。

歴史的な視点でいえば、結果的にアプリケーション例外とシステム例外はあまり区別しなくてよいという結論に至っている。アプリケーション例外、システム例外の2点で考えるとエンドポイントがクライアントとサーバーの2つのように見えるのだが、オペレーションシステムだとしてもはハードウェアに対してはクライアントになるし、アプリケーション内でもライブラリとユーザーインターフェースの区切りがあれば、ライブラリはシステムサイドと言える。なので中間層があるわけで、決定的な区別はない。特に、Java や C# のように VM を使う場合は、VM 内部での発生がシステム例外なのかアプリケーション例外なのか分離しづらいところもあるので、まあ、雰囲気的に例外を扱うという着地点に至っている。

そんな中で、システム例外をアプリケーションの表層(ユーザーインターフェース)に押し出すのがよいのか、という疑問がでてきる。「Good Code ~」で扱う Java の場合、呼び出す関数に throws を付けて「無視してはいけない例外」を明記することができるのだが、現在、他のプログラム言語で流行らなかったところを見ると例外の情報は呼び出し元にとってあまり重要ではないということらしい。端的に言えば、システム内部で発生した例外を細かく呼び出し元に通知していると、実に膨大な例外の数が渡ってくるために現実的に処理しきれなるためだろう。

そう、Google が開発した Go には例外がない。まあ、そういうことだ。

なので、なんらかの例外を呼び出し元に通知する場合は、

  • 有限である例外の種類

がひとつの基準になるだろう。例外の種類が5種類ぐらいならばいいけど、100種類ぐらい飛んでくるのであれば、それは何かクラス構造とかそもそも例外を飛ばす必要があるのか?というのを考え直した方がいい。この視点は必要になる。

例外通知はプログラムのバグ修正のためにあるのか?

これは C++ で例外処理が入った頃から思っていたことなのだけど、例外の通知は、誰のためにあるのだろうか?「Good Code ~」や当時の上司には「例外を隠蔽してしまうと、コードのバグが隠蔽されてしまう」ので、例外を隠さないに呼び出し先の関数では例外を呼び出し元に通知するという方針をとることが多い。

が、そもそも、実運用で動いているコードに対して「バグが隠蔽されるから」という理由で例外処理を潜ませておいて、例外が発生したらアプリが停止する(バグの特定のために?)というのはユーザーにとっては変な話ではないだろうか?

なので、私の方針としては、

  • 関数内で例外が発生したときは、エラーログを出力し、デフォルト値を返す(知らん顔をする)
  • ユーザー入力やデータ値でエラーになったときは、デベロッパー環境以外では異常値が入力されたときと同じように、通常のエラーメッセージと同様にユーザーに通知する(ユーザーの異常値なのかシステムの異常なのかは判断がつかない)

というコードにするのがベターと考える。もちろん、システムの方針によっては例外を発生してアプリケーションを停止させることもあるのだが、たいていの場合は停止させない。というか停止できない場合が多い。

  • 組み込みシステムの場合は、内部エラーが発生してもリセットする「人間」が不在のことが多いので、ひとまず動作を続けるパターンが多い。
  • 基幹システムの場合、止めることができない場合が多いので、勝手にアプリケーションを落とさない。応答はするものの、エラーメッセージを大量に吐き出すという対処で、デベロッパーに通知を送る。あるいは異常な状態に陥っても、メールを送る、通知を送る等の手段を残す。
  • ロボット系の緊急停止は、動作プログラムは別につくらないといけない。異常が発生した場合は、アームの位置を危険ではない状態に戻したのちに、停止するというスタイルになる。
  • もし、原子力関係のソフトウェアでバグが発生した場合、緊急停止するのではなく、現状維持がベターである。本当の緊急停止は監視者が手動で行えるので、ソフトウェアは閾値の加減となる。

このように製造業のソフトウェアシステムの場合は、プログラムのバグの追求よりも、安全にシステムが停止するという意味での「例外処理」が優先される。例外に対処するのは、利用者を守るために安全に停止する(あるいは動作を継続する)のが目的のなる。これは「フェールセーフ」の考え方になる。

xUnit 以前ならば、バグが隠蔽されるという意味もわからなくもないが、実運用しているソフトウェアに対して開発者の利点のためだけに「例外」を使うのはどうかと思われる。利用者の利点を最大限にするなれば、

  • プログラム内の回復不可能な異常発生時には、ユーザーに通知をして判断を仰ぐ。
  • プログラム内の回復可能な異常発生時には、エラーログを出力しユーザーには通知しない。

という使い分けが必要と思われる。「Good Code ~」では、後者の部分が省かれてしまっている問題がある。

bool login( string username, string password ) {
	
	if ( username == null ) return false;
	if ( password == null ) return false;

	// ログインチェック
	...
}

たとえば、login という関数で、username と password が渡されて bool を返すとする。

このとき、何らかのプログラムミスで、username や password に null が入っていてた場合は、例外を発生してプログラムの開発者に「login 関数の呼び出し元が異常なことをやった!」と通知したほうがいいだろうか?

私はそうは思わない。プログラムのコードのミス(それが login 関数を作った開発者ではなく、login 関数を呼び出す別の開発者であったとしても)を運用時にユーザーに通知するのは不親切であろう。もともと、login 関数の目的はユーザー名とパスワードのチェックなのだから、ユーザー名が null の人はいないので、しれっと false を返して、ログインできないようにすればいいだけだ。呼び出し元がバグのままかどうかなんてユーザーには知ったことではない。login 関数は例外を発生するのではなく、エラーメッセージを吐く位がせいぜいだろう。あるいは、C言語の assert 関数のように、リリースビルドをするときには影響しないものを考えるべきだろう。login 関数には bool 値(ログインできるかできないか?)の判断を任せているだけで、ユーザー名が null かどうかは尋ねていないのだから。

カテゴリー: 開発 | Good Code 書籍の例外処理とフェールセーフ思想の違い はコメントを受け付けていません

chu言語の配列とクラス

配列

💋言語の配列は

let x = [1,2,3,4,5]

のようにカンマで区切ることができないので、

👼🍎👈👫1😀2😀3😀4😀5👫

のようにブロック👫と区切り文字😀を使う。

クラスか構造体

いまのところ Rust の構造体に近い形で、データのみ扱う

🐱🍱🍊🌸🍱

この意味は

struct cat {
var orange
var cherry_blossom
}

のように cat クラスで、orange と cherry_blossom というプロパティを持っているこという意味である。オレンジとサクラは変数名(プロパティ名)なので、多分、型が「くだもの」と「花」なんだと思う。「猫」と「オレンジ」と「サクラ」は、ユーザーが自由に決めていい変数(ただし絵文字1文字に限る)。クラスや構造体は🍱で区切る。

型をどう指定しようか?string, number, boolean 程度が指定されてあれば十分だと思う。また、JavaScriptのように型なしでもよい。

struct cat {
var orange : string
var cherry_blossom : number
}

これを

🐱🍱🍊🖖🎻🌸🖖🎵🍱

に変換する。明示的に型を指定したいときは、🖖で区切る。そして型として🎻の string や、🎵の number を使う

カテゴリー: 開発, chu | chu言語の配列とクラス はコメントを受け付けていません

chu 言語のパースと改行

💋言語は、1文字でひとつの単語をあらわす。構文はF#に寄せる。ということで、

👼🍎👈10

let x = 10 ;

となる。

中間言語としては

👼🍎👈10

これを

:let: “apple” “=” “10”

に直したうえで

let apple = 10

にすれば簡単でよい。

F#の構文と1対1になるとは限らないので

🤔🍎🤝10🙆OK🙅NG

の場合は

if apple = 10 then OK else NG

になるが、

“if” “apple” “=” “10” “then” “OK” “else” “NG”

から直すことになる。この場合は、then と else がはいっているからうまい例ではないが。

あとで変更

文の扱い

💋言語では、単語の区切りに空白を用いない。できれば、改行も使わないようにしたいののだが、あまりいい思いつきがないので、

👼🍎👈10
👼🍊👈20

という2つの文を1行に書きたいときは

👼🍎👈10😀👼🍊👈20😀

のようにする。ちょうどC言語の「;」と同じ働きをする。

😀😀😀😀😀

は空行を示している。

ブロックの開始と終了

F#の場合はブロックの開始と終了をインデントで表すが、💋言語にはインデントというかそもそもタブとか空白とかいう概念がないので、なんらかのブロック(スコープ)が必要になる。

👫👼🍎🤝10😀👼🍊🤝20👫

こんな感じに👫 で囲うのがいいのだが、どうせならば対になるようにしたい。sh の if と fi みたいに男女を入れかえることは可能なのか?

カテゴリー: 開発, chu | chu 言語のパースと改行 はコメントを受け付けていません