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

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

この本は、いわゆる「はじめてのアジャイル開発」ということで開発者とプロジェクトリーダー向けに書いた本です。具体的なプログラミング技術要素は皆無なのですが、特定のプログラム言語やフレームワークに関係なく使えるノウハウ集です。基本は、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 言語のパースと改行 はコメントを受け付けていません

不具合票の書き方

おそらく、急遽新人に不具合票の書き方を伝えないといけない気がするのでメモ書き。

もともと新人教育では一連のソフトウェア開発を教えるので、PMBOKに従い、

・要件定義
・設計(外部設計、内部設計、詳細設計、画面設計など)
・コーディング(単体テスト込み)
・結合テスト(不具合票の取り回し込み)
・システムテスト、運用テスト
・運用保守に関する手順書

あたりを網羅するようにしている。なにか、品質が悪くてどうしようもなくなっている場合は、初手として上記の PMBOK のプロセスが欠けていないか確認する。各プロセス自体は必須という訳ではないが、視点として落としてしまうとそこにハマるという特性がある。なので、俯瞰して網羅的に知っておいて損はない。知っているが、時間や予算の関係で「やらない」という選択肢を敢えてとるし、あるいは簡略化・省力化する。あるいは、うまくいかないときは原点に返って、組み込むことを考える。まあ、それが PMBOK のナレッジな処な訳だし。

不具合票の取り回し

不具合票は主に「結合試験」のような、複数名が関わっているときに必須となる。

・テスター
・コードを書いた人あるいは直す人
・設計書を書いた人あるいは仕様を解説できる人

3つの役割(設計→コーディング→テスト)はひとりで担ってもいいし、設計とコーディングがひとりでもいいのだが、そこそこの規模になるとこのプロセスは3分割されて少なくとも3名以上が関わってくる。

不具合であれ設計であれ、3者の中で情報をとりまわすのだから、なんらかのコミュニケーションが必要となる。伝わるならばメールでもいいし、口頭でもいいし、文書でもいい。

しかし、3者が同じ時間帯で働いているとは限らないし、不具合の直しがシーケンシャルになるとは限らない。むしろ、テストや直しは同時並行的に行われる。なので、不具合の情報をいったん取り回しができるように(居酒屋の伝票のようなものだ)しておくのが必要ある。

不具合票には、

・何か起こったか?
・いつ起こったか?
・再現するにはどうしたらいいか?
・どういう風に直せばよいのか?

が明確になるように記述する。

なので、不具合票には、

・テストをした人の名前(コードを直すときに現象を聞く相手)
・テストをしたときの日時(コードのバージョンが必要)
・何をしたら、どうなって、こうなったのか(再現性の手順、あるいは再現しにくいことを示す)
・現状と期待値を示す(現状の誤っている状態、そして期待する状態を明記する)

があればよい。これに加えて

・テスト仕様書の番号(あれば)
・不具合票の番号(問い合わせのときに便利なので)
・不具合票のタイトル(問い合わせのときに便利なので)

というものがあればよい。

最近は修正したコードを Git などで管理することが多いので、

・不具合票番号あるいは試験番号
・コードに書かれている試験番号、あるいはコミットするときの番号

を一致させておくと検索しやすくなる。

不具合票のフォーマット

平易なテキストでも構わないし、適当なスプレッドシートを使ってもよい。ただし、Excel の狭いセルだけで取りまわそうとすると、画面キャプチャや手順が書きづらくなるのでやめておいたほうがいい。現在では GitHub の isseus の活用が一番楽だが、まあ、そのほかのバグトラッカーをつかってもよい。

  • 不具合票の番号
  • 対応する試験の番号
  • 不具合票のタイトル
  • 試験をした人の名前
  • 試験をした日時
  • 試験対象のコード名(バージョンとか)
  • 不具合の現象(動かないことを具体的に記述)
  • 不具合の再現手順(箇条書きで記述)
  • 期待される動き(試験項目へのリンクでもok)
  • 合否判定
  • 不具合を対処する人(だれが直すのかわからなくなるので。一般にコードを書いた開発者だが、テスターから開発者が見えない状況があるので注意が必要)

これに加えて

  • コードを修正した人
  • コードを修正した日時
  • 再試験をした人(たいていは試験をした人)
  • 再試験をした日時

を不具合票を直すときに記録をすればよい。

このあたりの手順は、車の修理とかパソコンの修理票とかを真似すればよい。

不具合票をカウントする

いわゆる、コードに中に含まれている不具合は複数の不具合票としてあらわれるかもしれない。また、対象の不具合を直さないと他の試験が進まないかもしれない。

この現象は、ワインバーグの言う「ブロッキング」に他ならない。

なので、不具合票がたくさん出たからといって品質が悪いとは限らない。不具合票に対処すればソフトウェアの品質は上がるわけだし、不具合票が0件だとしたら、潜在的な不具合があって見つからないのかもともとコードの品質がよかったのか(仕様や設計との齟齬も含めて)実はわからない。極端な話をすれば、テストを全くしなければ、不具合票は0件だし、不具合も出ない(見えないだけなのだが)のだ。

昔は、不具合票の数を指標として扱っていたものだが、「不具合票が多い」≠「コードの品質が悪い」とは限らないの注意が必要である。不具合票が残ったままであれば、コードの品質が悪いのは確かなことなのだが。

そういえば、富士通 Japan の時間 ID の件は全社展開(というか協力会社全社展開?)になったっぽいのだがどうなっただろうか?

何の目的でテストをするのか?

基本、テストプロセスはその前工程にあるプロセス(要求、設計、コーディング)をチェックするために行う訳で、対応としては

  • 要件定義(Rquirement)に対する、システムテスト/運用テスト
  • 設計(Design)に対する、結合テスト
  • コーディング(Coding)に対する単体テスト

に分かれる。

一般的に再帰テストが行われるのが、コーディングと単体テストのワンセットで、これが XP のプラクティスのひとつになる。ただし、昨今はモックアップなどをつくることによって、設計をチェックするための結合テストも自動化を行うことも可能となっている。

他にユーザービリティテストやセキュリティテストがあるが、これが別のものと考えてよい。要件定義におて機能外要件としてユーザビリティ(Web APIの応答が何秒以内とか)を求めることもあれば、漠然と画面の色やパーツの配置を求める場合ので、それぞれのプロジェクトによる。

要件定義書が顧客の求める要件であり、設計書は要件定義をシステム的に満たすための設計(建築でいうところの設計図)になるので、視点の向きがことなる。

このため、所謂「不具合票」が目的とするところは、

  • 要件を満たしているかを、システム的にチェックする
    → 要件が満たされない場合は、要件を満たせない設計になっている可能性が高いので、設計書を見直す
  • 設計を満たしたコーディングがされているか(詳細設計を含む)チェックする
    → 設計通りにできていない場合は、設計通りに直す
    → 設計通りに作ったとしても、「設計通りにはコーディングできない状況」、「設計が要件を満たしていない」場合もあるので注意が必要。
  • コーディング自体のミスなので、コードを直せば不具合とはならない

という3つの分野に分かれる。

要件を満たすかどうかのシステム試験の場合、ウォーターフォール開発の場合は、この時点で不具合が発生してもなんともならないことが多い。そうならないように、適宜スパイラル開発か、アジャイル開発か、マイルストーンを使ったウォーターフォール開発に直す。ちなみに、富士通 Japan の ID 重複の件は、この部分に属する。要件を満たさない設計を作ってしまったか、要件そのものに「時刻を ID とする」と書かれていたか。肥大化する要件定義書を抱えている場合は、後者となることが多い。特に F の場合は。

自動化する単体試験においては、XP のプラクティスのようにテスト駆動をしてもよいし、コーディングをしながらテストをしてもよい。昔のように、コードをビルドしてリリースしなければ画面が動作しないということはないので、画面を動かしながら Web API の動作チェックも可能である。かつ、そのように設計とコーディングの順序を決めておけば、不要な不具合票の取り回しをしなくてもよい。労力が減る。Vue.js とかで画面を作っている場合は、常にエラーが出ない状態でコードを組む方がよい。かつ、その時々で画面の動作を画面設計書などと照らし合わせながらチェックをすれば、過大なテスト項目をこなさなくても済む。

システム試験と単体試験の間にある結合試験では、当然のことながら「単体試験である程度の品質を確保しておくこと」が重要になる。いわゆる、コードの品質が悪いと、結合試験の進捗度合いにもかかわるし、そこでブロッキングされてしまう。つまりは、ここに「Good Code ~」における「コードの品質」が関わってくる。コードが高品質か低品質かの前に、

  • 結合試験をスムースに行えるだけのコードの品質が保たれているか?

という具体的な品質基準がここに出てくる。結合試験がスムースというのは、不具合がゼロというわけではない、設計ミスもあれば設計の読み違いもあるので不具合がゼロにはならない。ただし、そもそもコードが動いていないとか、コードを修正しようとしたときに誰かひとりに頼らないといけない(コメントがない、コードが読みづらい、コード分割され過ぎているなどなど)状況に陥るときは、明らかに「コードの品質が悪い」と言ってよいだろう。

というわけで、3つのテストプロセスにおいては、何の目的でテストを行っているのか?の違いを明確にしたうえで進めるのがよい。

カテゴリー: 開発 | 不具合票の書き方 はコメントを受け付けていません

絵文字プログラム言語 chu 構想はじめ

実は構想だけは10年前ぐらいからあるのだけど(言語名自体は一発ネタだし)、今朝方、円城塔「文字渦」を読んで絵文字≒表意文字1文字という縛りでこだわってみてもいいのでは?と思って思考実験してみる。

もともとの発想として関数言語を絵文字であらわすというのある。

let x = 10 ;

これを

😇🍎👈10

のようにする。

print x ;

💋🍎

のように変換する。

if x = 10 then "ok" else "ng"

🤔🍎🤝10🙆ok🙅ng

になる。

単語や数字は面倒なので、英数字をそのまま使って、文法はすべて絵文字の1文字であらわす。1文字しかないので、単語の区切りとかで半角スペースはいらない。print あるいは console.log が💋なのは、chu 言語だから。

プログラム言語の意味論を考え直す

絵文字1文字縛りにするのは、構文解析が楽というのもあるけど、制御文なので使われる「if」や「for」などの単語は、ひとつの単語がひとつの意味を成している(この場合は何らかの制御をするという意味で)と考えられる。例外としては、for … in のように2つの単語でひとつの制御を示すこともあるが「繰り返し処理」という制御の拡張(inが補助的なもの)と考えることもできる。

となれば、ひとつの制御そのものは、ひとつの単語すなわちひとつの表意文字=絵文字に対応させることが可能で、いわゆる予約語といわれるものは、すべてひとつの絵文字に置き換えられるのではないか?というのがある。

さらに、変数とは何を指している(特に変更できない immutable な変数)というかというと、文字列や数字、長い文章などのひとつのまとまりを、ひとつの単語(変数)としてあらわしていることになる。これは、構文解析の制限にもよるのだろうが、変数はひとつの単語としてあらわすようになっている。例えば「hello」という変数は作れるが「hello world」という空白を含んだ変数を作ることはできず「hello_world」のようにアンダーバーをつけることでひとつの単語にする。例外としては「’hello world’」のようにシングルクォートなどを使いひとまとまりにすることも可能ではあるのだが、これはシングルクォートに囲まれた範囲が、ひとつの単語に相当する、と考えられる。

また、ラムダ式を変数に保存するとき(C# だけどこんな感じ)

let func = () => {} ;

これは func という変数に、ラムダ式を割り当てている。つまりラムダ式(あるいは関数、あるいは一連の処理)をひとつの単語に集約されているといえる。たとえば

let greet = () => { return "Good morning" }

このようにすれば、greet() によって、Good morning という文字列が返される、あるいは一連の処理を記述できる。これは、一連の処理を関数名に集約している「構造化」という仕組みになる。そう、プログラムの構造化というものは、複数の処理をひとつの単語に集約させるという意味論がある。

さらに拡張すれば、オブジェクト指向のクラスやインスタンス(オブジェクト)も、複数の変数や処理群をひとつの単語にまとめるという意味論がある。

となれば、あらゆるプログラムは、無限に絵文字があるとすれば、ひとつの単語に集約できるのではないか?という思考実験が可能になる。まあ、実際、ターミナルの「コマンド」がそれを担っているわけで、できるわけですが。絵文字を無限に作ることは可能だけど、無限の絵文字を覚えることは不可能なので、「実現可能ではありますが現実では不可能ですね」というやつです。

シェークスピアの「ハムレット」の長い文章は「ハムレット」というタイトルを表す、ひとつの絵文字に集約できるだろう。

ということです :)

カテゴリー: 開発, chu | 絵文字プログラム言語 chu 構想はじめ はコメントを受け付けていません

新人教育のための Good Code, Bad code のメモ書き

一読してみて、これならば新人研修に使えるかもしれないと思いつつ、同時に研修に使うときには「元ネタ」を明確にしないとミスリードっぽくなるだろう。と思われたので、そのメモ書き。

参考文献のほうに

  • リファクタリング
  • デザインパターン
  • Unit Test

が書いてあるので、XP のプラクティスに合わせてもいいと思われる。

コードの品質とは何か?

日本語でいうとこの「品質とは」に関係してくるので、「高品質なコード」と「低品質なコード」の比較や、「1.2 コードの品質にゴール」に書かれている4つの箇条書きのように漠然としてくると、新人(特にコードを書く前の新人)にはちょっと困った自体に陥るんじゃないかな、と思う。

ここでいう「正しく動くコード」というのは、動くべき仕様書に対応するコード、という隠れた意味がある。細かく言えば、動くべきことを記述した「仕様書」と、実際に顧客が想定している「動作」とは乖離があるわけで(あの例の顧客が求めていたものの図)漠然と、「正しく動くコード」あるいは「正しく動作し続けるコード」が先にでてくると、混乱するのではないか?

たとえば、建築で言えば、「正しく動くコード」というのは、家の屋根に相当する。

雨が降っても部屋が濡れないように屋根を付ける、という要望を出したとしよう。屋根があればいいので、実は柱が1本でもいい。傘のようなものでもいい。雨の量はここでは問わないので小雨をしのげるだけでもいいかもしれないし、台風に耐えるような屋根にする必要もあるかもしれない。

だから「雨に濡れないように屋根をつける」ようにコードを書くこともできる。エラー処理をつけないようなコードや、例外を受け付けないようなコード。ある程度の範囲は許容するけど、極値では落ちてしまうかもしれない。でも99%のデータは、中央値にまとまっているから、たいていの場合は「正しく動くコード」として扱うことができる。たまに、極値が来ると正しく動かないコードに陥ってしまう。

だから「正しく動く」の部分は範囲が広い。もう少し修飾するならば、「おおむね正しく意図的に動くコード」というべきだろうか。

「おおむね」というのは、画面からの入力はおおむね大丈夫な感じとか、マイナンバーカードを入れたときに氏名に外字が使われていない限りは大丈夫とか、半角カナが入ってない場合は大丈夫とか、そんな感じで、「おおむね」正しく動いている状態。

プログラマから言えば、この「おおむね」正しく動いている状態というのは、到底「正しく動く」の範疇に入らないように見えるのだけど、少なくとも、想定している数字やひらがなを入れたときにデータベースにきちんと保存されているね、ぐらいの気持ち、いわゆる「正常系」がきっちりと動いているのを「正しく動くコード」と緩い形で決めてしまってもいい気がする。

でも、プログラムというものは正常系だけで済むものではない。コードのほとんどは異常系や例外をうまく処理するためのものが多い。その部分が「正しく動き続けるコード」にあたる、と思われる。

正常系でうごいていても、何かの異常系で動かないことがある。これは正しいコードと言えるか問えわれれば、言える。だって、正常系が動いていて、顧客の要望もおおむね動いているわけだから、入力するときにちょっと注意を払えばだいたい正常に動くわけだから「正しく動くコード」といってもいいだろう。しかし、「正しく動き続けるコード」とは言えない。

そう、雨をしのぐための傘のような屋根を付けて、正しく雨がしのげる住宅、と言っていいようなきもするし駄目のような気もするのだが、少なくとも「雨をしのぎ続ける」ことはできない。そういう意味では、ちょっとした小雨のような雨ならばしのげる正常系かもしないがい、横から吹き込むような雨には無力な屋根だ。少なくとも壁をつけないと雨をしのぐという住宅の意味をなさない。これが「正しく雨をしのぎつづける」住宅と言えるだろう。

だから「正しく動くコード」でキーポイントになるのは、顧客の要求を正常に処理ができるか、緩い基準になる。コードという視点から言うと発散してしまいそうだが、仕様書とか要件定義という意味においては、まず第一に正常系の要求をきちんと処理できること、ひとまず例外などを除いた機能要件が満たされることが正しく動くコードというよりも「正しく動くソフトウェア」あたると思う。

ただし、このままだと品質工学的な意味での「品質」に達しない。低品質ということになる。

では、正しく動き続けるコードあるいはソフトウェアは何に気を付ければいいのか?ということになる。少なくとも、ちょっとした例外処理(外字を使うとか、半角カナとか)は対処しないと品質が高いとは言えないだろう。それは、ある程度の例外を許容して正常に動くというものがある。「ある程度の例外」というのがキーポイントになると思われる。

カテゴリー: 開発 | 新人教育のための Good Code, Bad code のメモ書き はコメントを受け付けていません