はじめに
遅くなりましたが明けましておめでとうございます。
年も明けて一山当ててやるぜと開発に勤しんでおられますでしょうか。
わたしは、モチベーションが一番高いであろう年始に何かをやらないと一年何もやらないかなと軽く怯えて何か作ることにしました。
一方で、年始に考えたアイデアは謎の無敵感があるので、みんな大好き PoC(Proof of Concept:概念実証) を実施して開発継続可否を検証することにしたのでその話を書こうと思います。
ブレストは、ガチでやるなら 【MindMup2】 の方がよいかと思いますが、今回は 【Draw.io + VS Code】 で。
- ダブクリで新しいハコを出せる
- ハコの横クリックでコネクタ+新しいハコを出せる
- drawio.png に変換すると drawio で編集もできる png になる
家計簿的な何か
ということで、家計簿的な何かを作ることにしました。
今年こそはきっちり金管理するぞとか。いい感じのウェブアプリがあれば管理も捗るんじゃないの?とか。アイデアが年始っぽいですね!
家計簿だったら星の数程便利なアプリが出ているんだからそれを落とした方が手っ取り早いんじゃないの?と思う方もいると思いますが、個人的には、今までいくどとなく年が明ける毎に家計簿的なものに手を出そうと考えたり考えなかったりしましたが、続いたことはなかったという問題があります。1
ではなぜそんなものをわざわざ作るのか?
自分で苦労して作ったアプリだったら、愛着ももてるし最高に快適なUIを作れたら記録を続けられるかもしれないと、年始なのでそう考えることにしました。
何が面倒くさいのか?タイピングか?だったら、レシートから読み込みができれば、入力も簡単だし、スマホでホイホイ入力して続けられるかもしれない。課題分析も完璧ですね。
ついでに前々から興味があったGoogle Cloud Vision AIのOCRも調べられるしよさそうです。
PoC(Proof of Concept:概念実証)とは
アイデアを実際に利用可能なアプリケーションとするためには、要件定義やら外部設計やら色々なフェーズを経て開発をしてテストまでしないといけません。つまりは膨大な時間(=金)がかかります。個人開発で休日にDIYするのであれば支払いは発生しませんが、有限な時間の消費は避けられません。
つまりは、わたし達はアイデアを無限に形にすることはできないのです。
そこでPoCの登場です。
年始のテンションで思いついた最高のアイデアが形になった時に必ずしも最高のアプリケーションになるとは限りません。アイデアのうちキーになる部分をプロトタイプ作成などで検証し、コンセプト通り進めれば最高のアプリケーションにできるのかを判断するのがPoCです。
今回ば 『最高に快適なUI』 が作れなければ継続利用は見込めません。また、市場としてはバキバキのレッドオーシャンで、クレカや交通マネーとも連携している超高性能アプリが基本無料で存在します。
自分で継続利用する気もなく、売れる見込みもないアプリケーションの開発を継続するのは人生の無駄遣いです。
ここまで見るとかなり旗色が悪い案件ですが、DIY開発の傍らで お仕事問題集 を作成し git にて無料公開をしてたりします。
そのため PoC の結果が開発中止となっても無に帰する事はないので、安心して作業を進められました。よろしければ git にスターをつけて 供養してやってください。
PoC の意味について
PoCをやる意味は損切りを早くすることだと思っています。
絶対にやってはいけないのは継続する意味がないと結果が出たアイデアにしがみつくことです。コンコルド効果の様なことになっては絶対にいけません。お母さんから、教わらなかった、ですか?
とは言え、リリース日ありきのプロジェクトで PoC を実施している場合、中断の判定はものすごく難しいです。もう一度 PoC をやるつもりでスケジュールを立てること自体が難しいからです。
さらには、そこまで投入した費用、再度0からPoCを行うメンタル的な負荷と費用などを考えるとより一層難しくなることでしょう。
ネガティブな結果が出た時には 「無駄なことを続けないで済む、新しいことを始められる」 とポジティブに受け止めて切り替えていくことが大事です。長期的に見ると、無駄だと分かって続ける方がメンタル的に多大な負荷がかかります。
アイデアに固執しないためには?
また、人間は自分が思いついたアイデアを形にしないと、そのアイデアに脳を占拠されてしまうためか新しいことを考えられないという性質があるようです。
この呪縛から逃れるためには 『アプリをリリースして GoogleAnalytics とにらめっこしたけど全然ダウンロードされてない辛い』 といった経験を何度もするなどが近道だと思います。
いちいち形にしなくても脳内で消化して新しいことを考えられるようになっていきます。逃げたら一つ、進めば二つです。
PoC : レシートの読み取り
OCR(Optical Character Reader : 光学文字認識)を利用してレシートの読み取りを行います。
Google Cloud Vision AI を用いると、月1000回までは無料でOCR解析を利用できます。個人で利用する家計簿であれば、よほどの浪費家ではない限りは利用上限に達することはないでしょう。
ということで、まずは 公式サイトのデモページ にアクセスして適当なレシートを読み込ませて結果を確認してモチベーションを上げましょう。
勘のいい子はこの時点で致命的なユーザビリティの悪さに気づきそうですが、開発者モードのわたしは気づきませんでした。ユーザーの目線と開発者の目線を絶えず使い分けられるように知覚力を上げておかないとですね。皆さんは気づいたでしょうか?
ざっと見た感じ、nodejs で非常に簡単に使えそうだったのでPoCを継続することとしました。
ちなみに以下が全コードです。とても短いですね。
const fs = require('fs');
const vision = require('@google-cloud/vision');
const client = new vision.ImageAnnotatorClient();
async function main(fileName) {
const [ result ] = await client.documentTextDetection(
fileName,
{
'language_hints': ['ja']
}
);
fs.writeFileSync('./receipt.json', JSON.stringify(result), 'utf-8');
}
main('./receipt.jpg');
APIキーを食わせる方式ではなくて、サービスアカウントを食わせる方式になっていたのが若干面食らいました。
進化が速い上にWebから古くなった情報が消えないので迷走しがちなので、まずは公式サイトを熟読する方がよいかもしれません。
工夫した点は、PoCではOCR解析部とUI部を別作成にしたことくらいです。UIの改修のたびにOCR解析を行うと利用上限にあっという間に到達することが懸念されたためです。
GCPでの設定の詳細はこちらにスクショつきでドキュメントを残しておきました。リポジトリをダウンロードして VS Code + Markdown Preview Enhanced で見てください。
PoC : UI作成
技術スタックはこの辺です。今回はUIの検証を色々行いたかったので慣れているものを使うことにしました。
- Nuxt.js
- Vuetify.js
- panzoom
- svg, polygon
前述の図の通り、デモには非常にきれいな認識結果が表示されていたのですが、APIで取得できるデータで同じ様な認識結果を表示するには以下の課題を解決しないといけませんでした。
- 文字単位でちぎれたデータで、単語とするには結合が必要
- 頂点情報が順不同のデータで、そのままでは枠線を描けない
特に、枠線を描画できる様に頂点を整形する処理を何と呼ぶか分からずグーグル先生にサクッと質問ができずググり方をだいぶ試行錯誤しました。
わたしが調べたかったものは数学的には与えられた集合を含む最小の凸集合
であり凸包(Convex Hull)
2 と呼ぶそうです。
普通に生活をしていて『凸』を意味のある文脈で使う日が来るとは想像していませんでしたが、とにかく単語さえ分かればあとはなんとか調べられます。情報を上げてくださってた皆さんありがとうございます。
以下DrawingUtil.toConvexHull()
は、[{ x: 0, y: 0 },...]
形式の頂点配列を渡すと凸包の頂点配列を返却するメソッド。
export default class DrawingUtil {
/**
* 凸包を求める
* ギフト包装法
* 1. 最もy座標が小さく、その中でxも最も小さいものを求めて注目点とする
* 2. 次の点を求めるために、注目点ほか全ての点との偏角を求めて、最も左にあるものを選ぶ.
* 3. 一周するまで繰り返す.
*
* @param {*} points
* @returns
*/
static toConvexHull(points) {
let convexHullPoints = [];
if (points.length < 3) {
return convexHullPoints;
}
let basePoint = points[0];
points.forEach(point => {
if (basePoint.y > point.y || ((basePoint.y == point.y) && basePoint.x > point.x)) {
basePoint = point;
}
});
let currentPoint = basePoint;
do {
convexHullPoints.push(currentPoint);
currentPoint = DrawingUtil.nextPoint(points, currentPoint);
} while(basePoint != currentPoint);
convexHullPoints.push(basePoint);
return convexHullPoints;
}
static nextPoint(points, p) {
let nextPoint = points[0];
for (let i = 1, max = points.length; i < max; i++) {
let point = points[i];
if (p == nextPoint) {
nextPoint = point;
} else {
let v = (p.x - nextPoint.x) * (p.y - point.y) - (p.x - point.x) * (p.y - nextPoint.y);
let ab = (p.x - nextPoint.x) * (p.x - nextPoint.x) + (p.y - nextPoint.y) * (p.y - nextPoint.y);
let ac = (p.x - point.x) * (p.x - point.x) + (p.y - point.y) * (p.y - point.y);
if (v > 0 || (v == 0 && Math.abs(ac) > Math.abs(ab))) {
nextPoint = point;
}
}
}
return nextPoint;
}
}
今にしてみると ChatGPT先生に聞けば解答してくれるような気がするのですが、それでサクッと解決できてしまうと脳の何か大事な部分が退化しそうで怖いなと思いました。
ということで、とりあえず一次検証までは完了です。
(PoC: UI画面)
※panzoom で拡縮して、黄色部分をクリックして登録予定
PoC : 判定
…一次検証のつもりでしたが、この時点でPoC終了判断をしました。開発プロジェクトとしては終了です。
技術的にもう少しやっておきたいところはあるのでソースの更新と、お仕事問題集用のドキュメント作成を行って終了となります。
先週の土曜日スタートで2回目の土曜日なのですが、今週の月曜日には終了判定が出てました。
平日は一切プログラムもプロトタイプも触ってないのですが「あ、こりゃダメだな」と思うイベントが月曜日にあった感じです。
業務プログラマが陥りがちな罠なのですが、システム開発の企画は 業務全体を見ないとできません。
システム開発とは、とある業務があって、その中の一部をシステム化(今風に言うとDX化)することで人々の暮らしを豊かにしようという話です。ですので既存の業務の一部をごくごく自然に置き換えた結果、目的が達成された(今回で言えば家計簿データが蓄積された)状態になる必要があります。
終了判定を行ったのはセブンイレブンで買い物をした直後です。
あのレシートが吐き出されてそのままゴミ箱に落ちる悪魔的UX。最高にクールです。
あのUXを無視してわざわざレシートを引っこ抜いて持ち帰って写真を撮ってそこからレコードを抽出する心理的コスト は圧倒的に スプレッドシートにメモるコスト より大きいと感じました。
製品のコピーとしては 「スマホでレシート撮影するだけで家計簿がつけられる!」 などになると思いますが、いやいやいやいや、誰がそんなもん使うんだって感じです。
きちんと考えてから手を動かせば、新年一発目からボツ企画で無駄なコストをかけないで済んだなと思いました。
おわりに
最近、「申し訳ない、御社をつぶしたのは私です。」 という本を読みまして、うまくいった話よりも、失敗した話と、どのような思考の流れでそこに至ったかということの共有が大事だなと思い本稿を起稿しました。
開発については「失敗」としていますが、OCRの要素技術やお仕事問題集は手元に残っています。逃げたら一つ、進めば二つ3です。
皆様もエンジニアルートを進んでアレコレ手に入れましょう。
おまけ1
営業A短編シリーズ
おまけ2
2025年にIT人材が43万人不足するらしいので、学習用の資料をgitで無料公開してます(不定期更新)。
よろしければどうぞ。
-
家計簿記の記録を続けられている方がいたら、よかったことやモチベーションなどをコメント欄に書いてもらえると参考にできてありがたいです。 ↩
-
機動戦士ガンダム水星の魔女の重要なセリフ。 ↩