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

「紙飛行機を飛ばす、あれ?今、馬鹿にしませんでしたか?」

「・・・、いえ」

いや、実際、馬鹿にしてしまった。というか、紙飛行機。一体どこがこのITプロジェクトに関係しているのだろうか、とドリーは思わざるを得なかった。馬鹿にする、というよりもソフトウェア開発のプロジェクトで『紙飛行機を作る』というのはどういう目標だろうか?なにか、ITに関係があるのだろうか?

「いや、無理もないです。紙飛行機なんって誰でも作れるもの。誰でも飛ばせるものと思うじゃないですか。10歳の子供が、いや幼稚園児だって紙飛行機ぐらい作って飛ばせるようなものです。飛ぶ原理はわからなくても、折り紙を折ってロケット型にしてぴゅーと飛ばすとか、イカ型の紙飛行機とか絵本にでも載っているじゃないですか。それを真似たら、誰でも作れる紙飛行機。そう、誰もが作れるのに、このプロジェクトで作るのに意味はあるのか?という疑問ですよね。当然です。当然の疑問ですよ」

と、急に3番さんが話に割り込んできた。いままで、キーボードとにらめっこしていた人だが、ええと本当の名前はぴくみんだったかピクミンだったか覚えていないのだが、1番さんと同じ赤いシャツに赤いズボンをはいている。けど、背中に番号が振ってある。いつ付けたのか分からないが・・・。

いや、3番と書いてあるけど、5番かもしれないが、まあ、ここはどうでもいい、非1番の人が話しに参加してきた。

「ええ、その、紙飛行機は何か特別なものなんですか?」

「いや、特別ではないですよ。誰でも飛ばせる紙飛行機だし、誰もが作れる紙飛行機です」

「・・・・」

「覚えていますか?子供の頃に紙飛行機がたくさん載っていた厚手の本があったじゃないですか」

「そうそう、小学生の頃に流行りましたよね。二宮さんの紙飛行機集ですよ」

「1ページかな2ページぐらいでひとつの紙飛行機ができていて、本自体が厚紙なっているんです。うまく切り抜いて接着剤で貼り付けるとゴムでよく飛ぶ紙飛行機ができるやつです」

「そう、頭の部分にクリップを付けておいて、割りばしにつけたゴムで引っ掛けて飛ばすやつですよね。非常によく飛ぶんですよねぇ。子供の科学だったか子供の化学だったか、大人の科学だったか忘れてしまいましたが、いろんな飛行機がありましよね」

「だえん型とか無尾翼とか普通の飛行機じゃない型もありました」

1番さんと3番さんが楽しそうに紙飛行機の会話をし始める。

そうだ。折り紙で作っている手軽な紙飛行機じゃなくて、子供、といっても小学生の高学年の子が作るような紙飛行機だろう。自分で作るとなかなか飛ばないのだが、あの本に載っているものをうまく切り抜いて貼り合わせると、よく飛ぶ、そう。

「ああ、思い出しました。あの紙飛行機ですね。100メートルぐらい一気に飛ぶやつですね」

「「・・・・」」

いや、違ったか。100メートル程度だったら、飛行機としては飛んでいるうちにはいらないかもしれない。そもそも、ジャンボジェットの全長が70メートル近いのだから、100メートルだったら飛ぶというよりも、ちょっとジャンプしてしまう位の感じだろう。そう、「岸和田博士の科学的愛情」にでてくる轟天号が25メートルプールで飛び込みをしたときに、ちょっと飛ぶだけで飛び込み台から25メートル先、つまりはプールの縁か縁まですぐに達して頭をぶつけてしまう位の感じだ。

だから、もうちょっと飛ぶかもしれない。例えば100光年とか。

「すみません、単位を間違えました。100光年ぐらい飛ぶやつですね。わたしの子供の頃にはやりましたよ」

「「・・・・」」

なにか不味いことを言ったらしい。いや、ドリーにとっては「地球のことはわからないから、ホコタテ星の基準しか知らないので」というところなのだが、それが通じる相手なのかいまのところは検討がつかない。

「いや、さすがに、100光年だとだめなんですが」

「もうちょっと、飛ばないと駄目ですよね、1万光年位ですかね?」

100倍位ちがっていたらしい・・・。

「なるほど、そうなると、紙飛行機の選定の部分から大変そうですね」

「おお、解ってくれるじゃないですか」

「そうなんですよ、たかが紙飛行機なんですが、材質から選定しないといけない。ときには、新しい材料も探していかないといけないというプロジェクトなんです」

「新しい材料となると、色々とありますね。「紙」飛行機という名前になっていますが、なにも紙じゃないと駄目という訳でもないのです。無限にある材料を紙のように薄く引き伸ばして、長距離を飛べるように形作っていくわけで、まさしく「神」飛行機ですね、はははは」

「はははは」

「はあ・・・・」

1番と非1番が談笑しているのをドリーは眺めているわけでが、談笑しているからといってプロジェクトがうまくいっているとは限らない。プロジェクト計画書があって、進捗状態が99%だとしても、シュローは困っていたわけで(いや、ドリーに無理難題を押し付けていてホッとしていたかもしれないが、あとで思い出してみよう)、このプロジェクトが終わる見通しが立っているかどうかが問題なのだ、とドリーは気を引き締める。談笑に騙されてはいけない。

「プロジェクト目標が『紙飛行機を作ること』はわかったのですが、そこに至る手順というか道筋を聞いておきたいのですが、よろしいでしょうか?」

「ということは、この紙飛行機作成プロジェクトを納得して頂けたということでしょうか?」

「あ、いえ、納得というか・・・」

世の中、不思議なプロジェクトというものはたくさんある。短期もあれば長期なものもある。スタートとエンドがあるからプロジェクトであって、繰り返し生産ができればそれはプロダクトの領域だ。紙飛行機を量産というと、なんらかの機械があって時間内にいくつ紙飛行機が飛ばせるのか、飛ばした紙飛行機を籠に入れるまでの競技があったりなかったりするのだが、その紙飛行機を折る機械を作ろうとするならばそれはプロジェクトだし、プロジェクトなりのノウハウを活用できる。

「まずは、その紙飛行機プロジェクトの内容を聞いてみないとなんとも言えないものがありますので・・・」

「ごもっとも、話は簡単ですよ。紙飛行機を目的に向かって飛ばそうプロジェクトです。このプロジェクトに既に3年も掛ってはいますが、全体の進捗としては99%であともう一息というところです」

1番は、壁に貼ってあるホワイトボードに図を書き始める。確か壁一面に付箋やら設計図やらが貼ってあった筈だが、その部分だけ1面まっしろとなってぬけている。不思議だ、と思ったら壁に貼ってある設計図の上に新しい紙を貼ったらしい。横からみるとどんどん紙が重なっていて地層のようになっているけど、重力で落ちないか不安だ。いや、そもそもここに重力があるのか?足が浮いているような気もするけど気のせいだろう。

「まずはここ、中央に『紙飛行機』があります」

典型的なマインドマップあるいはWBS(work breakdown structure)の類だろうか。なぜにITプロジェクトで紙飛行機なのかはわからないが、目的ははっきりしているらしい。

「次に『紙』、こっちに『飛』、こちらに『行』、さらに『機』があります」

1番は「紙飛行機」を1文字ずつ分解しているようにみえる。何?何がはじまったのだろうか?

「さらに『紙』は、ごぞんじのように『糸』と『氏』にわかれますね。『行』、これは簡単ですね。『ノ』と『イ』と『テ』に分けることができます」

まてまて。漢字を偏と旁に分解するわけではないらしい。一体何をやっているのか?

「さらに『テ』なのですが、『一』と『丁』に分けることができますね」

「あ、いちばんさん。『テ』は、確か『二』と『ノ』に分けたと思いますよ」

「ああ、そうでした。『一』と『丁』の組み合わせだと、なかなかコストがかかってしまうので、受託コストの安い『二』にしたんでしたっけ。こうすると『ノ』のところが共有部分になって製作期間が短くなりますからね」

「『ノ』をふたつ作ればいいのと、最終的に『二』は2つの『一』から成り立つことになるので、ここも繰り返し計算で済むというのが便利です。for文を使ってもいいですし、コレクションやリストをつかってもいいです」

コンピュータプログラム言語っぽい用語を出しているようだが、ドリーにはさっぱりわからない。単に漢字を分解してみせているだけだが、これはいったいなのだろうか?設計なのか?

1番が言う。

「ドリーさんには少し難しいかもしれませんが、もう少し解説させてください。ここが難しいところですし、このプロジェクトでは大切なところなのです」

「あああ、はい・・・」

「『飛』これが難しいところです。最初は『升』と2つ『て』にわけて、ニスイにしたいところだったのですが、このニスイがなかなか難しいのです」

「というと?」

「漢字変換で、なかなか出ないんですよね。「ニスイ」と書いても「にすい」と書いてもだめなので、よくよく調べてみると『冫』という形で書けるのです。部首としては「ひょうぶ」と読むらしいですが、ここでは『冫』としておきましょう。そうなると、『升』と2つの『て」そして2つの『冫』で構成されるわけですが、ここで問題が発生していまいます」

「・・・・」

「『冫』というのが、コミュニケーションしにくいのです。お客に説明するときにも『冫』の部分を読むことができないし、開発者の間でも『冫』を勘違いしやすくなってしまいます。ラショナル統一プロセスの辞書作成の基準なのですが、できるかぎり顧客つまりは利用者の言葉を使ってみたり、開発者の間で齟齬がないような用語をまとめておくようにする決まりがあるのですが、ここで『冫』を使ってしまうと、いちいちコピペしないと出てこない漢字・・・といいますか、部首、これを口に出していうのもなかなか難しいので、このプロジェクトでは『冫』を使うことを諦めました。混乱はプロジェクトの停滞のもとですからね。プロジェクトが潰れかねません。開発チームを崩してしまいます」

意味がよくわからないが、その、なんか「ン」に似ているものどうするかという話だろう。チームを混乱に落とし込むような要因は避けたほうがいいのは同意する。完全ではないが同意したいところだ。

「そういう訳で、『冫』を使うのはやめて『ン』にしたんです。ですが、『ン』にしても『ソ」と間違える人が多くて困っているのです。日本人だと大丈夫っぽいのですが、非日本人だと駄目なんですよね。『ン』と『ソ』を間違ってしまう。たとえば「ソン」と言ったときに、何故か「ンン」と伝わってしまったり、「ソソ」と伝わってしまったり、逆に「ンソ」と聞き違えてしまう場合もあるのですよ」

「はあ・・・」

「いや、非日本人というのは宇宙人や猿も含めるわけで、猿ならばいいんですが、宇宙人となるとちょっとやっかいで」

確かに厄介な問題になりそうだ。差別というか区別というか、日本人と宇宙人と猿を同列で扱っていいものなのだろうか?ホコタテ星人がはいっていなのがちょっと不満であるが、とドリーは思った。

「なので、『ン』は諦めて、『ニ』を使うことにしました。ちょっと字の形が違ってしまいますが、2つ『ニ』を使えば『ン』よりも『ソ』と混乱しないで済むと思われるのです」

「ですが、ちょっと注意してくださいね。ドリーさん」と急に割り込む非1番。

「勘違いしないで欲しいのですが、先の『二』と『ニ』では字形が違うことがわかりますか?最初の『二』は『一』に分解できる『二』なのですが、こっちの『ニ』は2つの『-』を使うのです」

違いが全く分からん。。。

「さらに細かくなるのですが、『-』と混乱しやすい『ー』を使うところが台無しになってしまうのです。見た目は似たように思えるのですが、縦書きになるとほら、こんな風に『1』になってしまいますからね」

兎も角、紙飛行機プロジェクトが混乱の極みになりつつあることはわかった。しかし、混乱しつつもうまくメンバーがアイデアを出してプロジェクト計画に沿って文書作りをしていることが判明した。ここは、もうひとつ『ニ』と『二』がいったい何なのかを詳しく聞かねばなるまい。

ドリーは若干の不安を抱えつつ、少し問題が解決したような全く解決していないような、けむに巻かれてしまった気持ちで、1番に質問をするのであった。

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

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

さて、一方、やってんどーの部屋でのドリーである。

プロジェクトメンバーを数えると、1人2人3人・・・と5人いる。まずはプロジェクトリーダーが誰なのかを見極めないといけない。ドリーの武器は「段取り」一本である。段取りの一歩目はまずは状況把握が大切である。

メンバーは5人。名前を聞いてみると

「ひくみん、です」

「びくみん、です」

「ひぐみん、です」

「びぐみん、です」

「ぴぐみん、です」

という答えが返ってきた。なんだ、オリマーはいないのか、と思ったドリーだが、某天堂ゲームではないのでオリマーがいるはずがない。いや、オリマー役がこのプロジェクトには必要なはずだが、実はだれかがオリマー役を担っているのかもしれない。

「段ドリー、です。よろしくお願いします」

自己紹介?は済ませたので、早速だれがリーダー役かを聞いてみる。

「この5名のうちの誰がリーダー役なんですか?」

「「「「「?」」」」」

なんとも怪訝な顔をした「ひくみん」達が並ぶんだが。いや、区別がわからない。首の傾げ方も同じだし、実は服も同じだ。赤いシャツを着ていて、赤いズボンをはいている。ほっそりした体系もみな同じだ。頭にぴょこんと馬鹿毛が生えている・・・訳ではないが、似た感じの髪型をしている。区別がつかない。

「ええと、リーダー役は、ひくみんさん、あなたですか?」

適当なひとりを指さして尋ねてみる。果たして、彼・・・あるいは彼女が、ひくみんさんかどうかもわからないし、リーダー役かどうかも分からない。両方が当たる確率は5×5で、1/25の4%というところで限りなく低い。

「違いますよ」

案の定、否定の言葉が返ってきた。

「そうですよ」

意外にも、肯定の言葉が返ってきた。

はずれが4/5、あたりが1/5の確率で事前確率と事後確率のベイズの関係だ。

「ひとまず、ひくみんさん」

「ええ」

「ちょっと、皆同じ赤い色のシャツを着ているし、顔もどこととなく似ているので、みなさん区別がつくないので、右から「1,2,3,4,5」と呼んでいいでしょうか?」

「・・・・」

ひくみん達が怪訝な顔をして顔を見合わせている。

あまり区別がつかないといはいえ、冒険ダン吉のように番号で割り振るのはあまりにもだと思う。でも、どうみても区別がつかない。A、B、Cでもいいし、い、ろ、は、でもいいのだが、さすがに番号は失礼だろうか。せめて、国語、算数、理科、社会とか、青、赤、黄、白、黒とか五行大儀風に揃えたらいいとかしたらよいだろうか。

「ええ、いいですよ」

「え?あ?いいんですか。ちょっと失礼かと・・・思ったんですが」

「いえいえ。無理もないです。地球の人たちには私達はまったく同じに見えるはずですよ。同じ赤いシャツと同じ赤いズボン、更に同じ顔に同じ髪型をしているから外見では区別はまったくできません」

「・・・・」

「なので、区別しようとしても無理です。無駄です。徒労です。お疲れ様なのです」

「あのう、ひとつ聞きたいのですが」

「はい、なんでしょう」

「あなたがた、ええと、ひくみんさん達はどうやって、区別されているんですか?」

「え?、どういうことです?」

「ええと、ひくみんさんと、ひくみんさん以外が区別がつかないとなると、番号を割り振る位しかないので、区別できなくて困りませんか?」

「はい、まったく困りませんよ。自分と自分以外を区別するのは用意だし、場合によっては区別する必要はありませんから」

「いえ、なんというか、たとえば、ひくみんさんとぴくみんさんを区別する必要があると思うのですが」

「いやいや、まったく大丈夫です。ここにいる人といない人ぐらいは区別がつきますからね」

「あ、ありがとうございます」

なんだよくわからないが、校正者が発狂しそうな感じだが、ありがたくもひくみんさんたちは番号で呼ぶことができるようになった。ありがたい。

「では、ええと、1番さん。最初の質問に戻るのですが、このプロジェクトのリーダー役は1番さんですか?」

「いいえ、違います」と1番が首を振る。

「じゃあ、2番さん?」

「いいえ、違いますよ」と1番が答える。

「そうなると、3番さん?」

「いいえ、そうではありません」と1番が再び答える。

「ひょっとすると、4番さん?」

「いいえ、そうではありません」と1番が再三答える。

「ええと、5分の1の確率で外れたということですか。そうなると、5番さんなのですね?」

「いいえ、残念ながら違うのです」と1番が言う。

「ん?どういうことでしょう?ひょっとすると、さっきのシュローがリーダー役なのでしょうか?」

「そんなことがある訳ないじゃないですか!」

最後の答えだけ1番は怒ったように声を荒らげる。何故、怒っているのかわからない。いや、そもそも顔も赤っぽいので(服やズボンの照り返しかもしれないけど)興奮している訳ではないかもしれないけど、口調が違うのは確かだ。シュロー、嫌われているのか?

「ちょっと、よくわからないのですが・・・、もしかしてリーダー役は今日はお休みなんですか?」

「いいえ。ドリーさん、ちょっと勘違いしているようですが。このプロジェクトにはリーダー役がいないんですよ」

「リーダー役がいない?」

「ええ、プロジェクトにはリーダー役がいません」

「リーダー役がいなくて、どうやってプロジェクトを動かすことができるのですか?船頭役がいなければ、プロジェクトは路頭に迷ってしまうし、船が丘に上がってしまうかもしれませんよ。いや、丘に上がるのは船頭役が多い場合だから、遭難してしまう船に例えたほうがいいですね。どこに行く付くかわからないじゃないですか」

「ええ、センドウ役ならいるんです」

「え?船頭役はいるんですか?」

「ええ、センドウ役は私です」

「・・・・」

船頭役がいるのにリーダー役がいないというのは不思議な話だが、ひとまず1番がリーダー役ということらしい。何かのプロジェクト独自のルールがあるのだろう。「プロジェクト」と「プロダクト」の微妙な違いを主張したり、「コンテキスト」と「コンテクスト」を区別したり、「顔認証」と「顔認識」をごっちゃにしてしまって叱られることぐらい意味があるものかもしれない。

「なるほど。1番さんがリーダー役・・・じゃなくて船頭役なわけですね」

「はい、センドウ役です」

リーダー役の1番の他にも確認を求めたほうがいいかもしれないとドリーは考えたが、他の忙しそうなプロジェクトメンバーを見ていると、このまま1番さんに話を聞いたほうがよさそうな気がした。他の4人はカタカタとキーボードをかき鳴らすことなく、椅子の背もたれに体重を預けながら瞑想しているように目をつぶっている。とても忙しそうだ。設計を考えているのか、小難しいロジックを考えているのか、それとも新しい発想を実現するための手順を頭の中でフル回転させているのか。いびきをかいたり、舟をこいだりしているメンバーもいるが、気のせいだろう。

「ところで、1番さん。このプロジェクトの目的は何なんでしょうか?」

プロジェクトの目的はプロジェクト計画書に記述されているはずだ。PMBOKにはプロジェクト憲章がある。しかし、あの「憲章」というのは何だったのだろうか?皆で意思を統一するための憲章だったのか、皆さん頑張りましょうのための集まりだったのか、それとも単なる飲み会だったのかさだけではないが、100年もの長く続いているプロジェクトにとってプロジェクト憲章とは一体何なのだろう。いや、100年プロジェクトのほうは憲章は必要かもしれない。

いきなり、プロジェクト計画書を見せてください、というのもアリなのだが、プロジェクト計画書がないあやういプロジェクトの場合だと「プロジェクト計画書とは何ですか?」とか「ふふふ、いまどきプロジェクト計画書なんて古いことをやっているんですか、ぷぷぷ」と笑われたりしそうなものだし。ここは円満に、目的を聞いておくのがいいだろう。でも、目的を知らないメンバーがプロジェクトに入ってくるのも問題だし。

「ここに、プロジェクト計画書があります」

1番は、机の上からプロジェクト計画書の冊子を持ってきた。

「ああ、プロジェクト計画書、あるんですね」

「ありますよ。当然じゃないですか」

「そうですね、当然ですね」

「当然ですよ」

ああ、この当然ができないプロジェクトがなんと多い事か!いや、冊子にしなくてもいいのだけどせめてプロジェクトの目的が明確になってから「プロジェクト化」することをして欲しいものです。漠然と営業目標からもってきたものは、達成基準がないからゴールがなくて「プロジェクト化」にならないんですよ。

とドリーの心の叫びで代弁をするのはこれまでとして、ドリーは冊子を開いた。

『プロジェクトの目的:紙飛行機を飛ばすこと』

「あの・・・」

「はい?」

「これが、プロジェクトの目的ですか?」

「ええ、これがプロジェクトの目的ですよ」

「紙飛行機を飛ばすこと、って書いてありますが」

「ええ、紙飛行機を飛ばすことがこのプロジェクトの目的なんです」

「・・・・」

前途多難の予感しかしない、とドリーは思わざるを得なかった。

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

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

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

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

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

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

唯一わかっているのは、出身地のところに書いてある「ホコタテ星」という文字である。出身地といえば、日本だとかアメリカだとか北極だとか書きそうなものだけど、「ホコタテ星」というのは初めてである。いや、そもそもが履歴書なんてもらったもののが初めてだし、初めての新入社員なのだから他と比較しようがない。まあ、出身地がどこだってかまわないのだけど、「ホコタテ星」というのは何処にあるのだろう。ペンギン村は日本にあるらしいけど、ホコタテ星は聞いたことがない。

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

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

私「ホコタテ星?」
ドリー「ホコタテ星です」
私「それはどこにあるの?」
ドリー「地球ではないことは確かですね」
私「いや、日本のどこにあるの?北海道とか?」
ドリー「いいえ、地球ではないどこかです」
私「・・・」
ドリー「禁則事項ですから」

いや、新入社員なのだから、禁則事項とか企業秘密とかいう問題ではあるまい。正体不明な新入社員が入ってきて、招待されてはいってきたのか、小隊に入りたいのか判断がつかないが。まあいいか。どうせ、社員は俺ひとりなのだから。

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

そう言い切ってしまわれると仕方がない。彼女、というか彼というか、ドリーは「ふんむー」と得意げな顔でこっちを見ているのだが、まあ、よい。会社に通えるならば問題ない。そもそも会社に通う必要もない。在宅勤務というわけではなくて、一気に現場に飛んでもらうのだ。

私「さて、新人の君の仕事なんだが・・・」
ドリー「え?新人の私が?」
私「ああ、新人だろう?この会社の新入社員なのだから」
ドリー「いや、新人じゃないですよ、ベテランです」
私「・・・」
ドリー「ベテランですよ。段取りのベテランです」

何を言い出すのかよくわからないが、ドリーはベテランらしい。新入社員だけどベテランだ。そう、何か便利じゃないか、ベテランの社員が入ってくれば会社として万々歳だ。早速働いて貰おう。

私「何?何のベテラン?」
ドリー「だから、段取りのベテランです」
私「段取り?何それ?」
ドリー「段取りですよ、ダ・ン・ド・リ。知らないですか?段取り!」
私「初耳だなあ・・・」

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

私「プランニングとか、ガントチャートとか、チケット駆動とかそういうやつ?」
ドリー「いえ、ダンドリですよダンドリ。段取り八分、電話は二番ってやつですよ」
私「何それ?」

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

私「まあ、ダンドリはさておき、新人の君の仕事の説明なんだが」
ドリー「ベテランです」
私「・・・・」
ドリー「ベテランです!」
私「まあ、いいや。ベテランの君の仕事ななんだが」
ドリー「むふー」

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

私「べつの部署で、「やってるどー」というのがあるのだ。いま、プロジェクトが進行中なのでそれのマネジメントを君にやってもらおうと思っている」
ドリー「やってるどー、ですか、なかなか楽しいプロジェクトそうですね」
私「そうだろう、なんとかやってるどーってことで、やってるんだよ」
ドリー「なるほど」

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

ドリー「つまり、そこでわたくしが段取りしろというとですね」
私「そう、察しがいいね。マネジメントして欲しいんだ」
ドリー「段取りですね」
私「そう、マネジメントなんだよ」
ドリー「段取り付けるんですね」
私「そう、わかった、そのダンドリってやつでいいよ」
ドリー「むふー」

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

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

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

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

私「やってますか?」

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

私「…やってますか?」

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

「やってますよ」と、返事が返ってきた。

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

声を掛けたのは失敗したかもしれないが、まあ、いつものことだ。何を言っているのかさっぱりわからないが、ともかくやっているのは確からしい。寝たりサボったりゲームをしたりしていたら、「さぼらないでくださいねー」と声を掛けることもできるのだが、忙しく仕事をしている人には声を掛けづらい。さらに、詳しく説明してくれるし、よくわからないけど何かができあがってくる雰囲気だけはあるので、その圧が強い。いやあ、いいものができてくるんじゃないかという感触はある。感触はあるのだが、3年間できあがらないのは、何故だかよくわからないのだけど。まあ、いいものができあがるのは確かなことだろう。信用第一だ。

私「で、進捗具合はどうなのですか?」
「進捗ですか、そう進捗ですね。ええ、進んでいますよ、かなり進んでいます。でも、なかなか難しいところがあるので、あと一歩ということろですね」
私「進捗率で言えばどうなんですか?」
「そうですね。99%というところでしょうか。。あと一息なんですよ。あと一息」

それはすごい。進捗率99%だなんて、あともう少しじゃないか。いつ終わるのか分からないけど、進捗率が99%なんだから、あと残りは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 書籍の例外処理とフェールセーフ思想の違い はコメントを受け付けていません