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 のメモ書き はコメントを受け付けていません

EntityFramework Core 7.0 を使ったときにサーバー証明書エラーが発生する場合の対処

.NET 6 で利用していた EF Core 6.0 を .NET 7 の EF Core 7.0 にアップデートしたときに「信頼されていない機関によって証明書チェーンが発行されました」のエラーが出てきて SQL Server に接続できないことがあります。これは、開発用に立てたローカルの SQL Server に接続するときに、サーバーの証明書が正しくない(自己証明書を含む)ときに発生するようです。正しくは SQL Server が動いているサーバー証明書をローカルPCにインストールするか、正しいサーバー証明書をいれればいいのですが、本運用ならともかくとして、開発用サーバーに正式な証明書をいれることはありません。

EF Core 6.0 ではサーバー証明書を無視するらしいのですが、EF Core 7.0 できっちりとチェックするようになったための問題のようです。

開発中の場合は、サーバーにある自己証明書(開発機で動いていれば自分自身のPC)をスルーするようにします。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    base.OnConfiguring(optionsBuilder);
    if (optionsBuilder.IsConfigured == false )
    {
        var builder = new SqlConnectionStringBuilder();
        builder.DataSource = "(local)";
        builder.InitialCatalog = "mvcdb";
        builder.IntegratedSecurity = true;
        builder.TrustServerCertificate= true; // サーバー証明書を常に信頼する
        optionsBuilder.UseSqlServer(builder.ConnectionString);
    }
}

DbContext クラスで、TrustServerCertificate の値を true にしておくと接続できるようになります。

config の場合は「TrustServerCertificate=Yes」を追加しておきます。

参考先

ssl – Entity Framework Core 7 connection certificate trust exception – Stack Overflow https://stackoverflow.com/questions/74467642/entity-framework-core-7-connection-certificate-trust-exception

カテゴリー: 開発 | EntityFramework Core 7.0 を使ったときにサーバー証明書エラーが発生する場合の対処 はコメントを受け付けていません

週40時間勤務と20%ルールでプロジェクト納期を厳守する

まだ会社にいたときに「なぜ、週40時間にこだわるのか?」と何度か上司に聞かれたことがあります。そのころはXP(エクストリーム・プログラミング)が始まったころで、当時はそのプラクティスのひとつとして「週40時間勤務」が重要だったのと、来日したケント・ベック氏に勢いで「ケント・ベック氏自身は勤務時間40時間を守れているのでしょうか?」と質問したときに、「文章書きのときは守れていない。けど、プログラミングをするときには週40時間が必須!」と強調されたから、という理由があったわけですが、実はもうちょっと別な根拠があります。

その後、日本のIT業界は少し不況に入ったので、仕事が減り少しずつ勤務時間も改善されてきたので、週の勤務時間にこだわる必要もなくなったのですが、たまに「炎上プロジェクト」をみるので、勤務時間の話を書き残しておきます。

正確な事前見積もりをあきらめる

大抵のプロジェクトで「炎上プロジェクト」(あるいはデスマーチプロジェクト)となるのは、事前のスケジュールミスが問題です。昨今の炎上プロジェクトはITプロジェクト自体が短期化しているところもあって、短い時間の中に開発を詰め込みすぎ、人数が足りなくて「炎上」ということが多いです。炎上とは言え、プロジェクトは完了します。たまに、大規模プロジェクト(IBMの特許プロジェクトのような)で炎上後にプロジェクトが崩壊するパターンもありますが、数か月から半年以内のWebプロジェクトやシステム開発のようなものは、炎上しつつも、なんとか納品するという(スケジュールは伸びるかもしれませんが)という結末を得ることが多いです。

半年以下のプロジェクトの場合、週で言えば、24週間になります。契約がうまく移動できるスクラムや顧客を含めたチケット駆動で行っている場合は、金額やスケジュールの調節でうまくできるかもしれませんが、たいていのプロジェクトでは、事前に金額やスケジュール(納品日)が決まっていることが多いです。

納品日がずれると、検収日がずれるので会社としては集金日がずれます。と同時に、伸びた分だけ社員に給与を払うことになります(派遣ならば、派遣先に延長を申し込むことになる)。この増加分や遅延分を顧客が支払ってくればよいのですが、そうもいきません。

その昔、ファンクションポイント法(FP法)などで見積もりの正確さを求めいた時期があるのですが、最近のアジャイル開発を含めて計画駆動でさえ、正確な事前の見積もりはうまくいきません。本来ならば概要設計まで行って機能別に見積もりをしたいところですが、事前の要件定義すらぐらぐらなところがあるので、そのぐらぐらの事前条件に使って概要設計をして精密な見積もりを出しても意味はありません。

アジャイル開発においては正確な事前見積もりを清く諦めます。諦めて、プロジェクトが進む中で機能の増減やスケジュールの遅延などを調節していきます。が、果たして、日本のIT業界の現状としてはそれはできないことが多いのです。

概算であれ事前の見積もりが求められ、ざっくりとした見積金額がそのまま正式な金額になりがちで、さらにサービスリリース日は顧客の営業の都合で決められることが多く、開発側の都合だけで金額も開発スケジュールも決められるものではありません。

プロジェクト予算とスケジュールを固定化する

以前にも何度か話していますが、プロジェクトの予算については二重帳簿方式を使います。二重帳簿というと聞こえは悪いですが、まあ、仕方がないです。

  • 顧客に提示するプロジェクト予算
  • 社内で開発するときのプロジェクト予算と実績

顧客や営業先に用意する「プロジェクト予算」は、見積もりを提示した突端に固定化されます。割引交渉で減額されることはあっても、増額されることはまずありえません。機能はそのままで、割引を要求されることも普通にあります。これは、営業の値段交渉のうちなので、実装するときの機能とは切り離しておくとよいです。

つまり、切り離したところに、実績としてのプロジェクトの内部予算があります。実際に社員や派遣さんなどが働いた工数を積んで、具体的に払う金額つまりプロジェクトで利用した金額を積み重ねていきます。

同時に、顧客に提示するスケジュールも提示した途端の固定化されます。納品日あるいはリリース日が決定されると、プロジェクト開発において前に倒すことはあっても後ろに倒れることはあり得ません。ごめんなさいするしかない場合は、後ろに倒れますが。

ここで「プロジェクトバッファ」を取って、プロジェクトのスケジュールを立てたいところですが、もう少し泥臭い方法があります。プロジェクトのバッファの方式は、バッファ自体を食いつぶさないの注意しないといけません。これは、チケット駆動やスクラムとの併用も可能(PMBOKにも指針があります)なのですが、常に「学生症候群」(夏休みの宿題を後ろに残すあれ)を気にかけてチームマネジメントをしないといけないので、途中で機能が増減する場合は、なかなかハンドリングが大変です。

プロジェクト計画時に月160hで計算する

小規模のプロジェクト(5,6名程度)で期間が半年以内のプロジェクトに限定するものですが、プロジェクトメンバの勤務時間を柔軟にすること(残業も含めてという意味で)を前提にして、プロジェクトのスケジュールを立てます。

このとき、週40時間、月160時間で見積もりをします。稼働日数を22日にしたり、忙しい月だからといって稼働時間自体を月200時間で計算してはいけません。あくまで、週40時間、月160時間で統一的に計算してしまうのがミソです。

プロジェクトバッファ(「保険」とも言う)の仕組みは簡単です。

  • 通常は、週5日で8時間勤務で行う
  • 少し忙しいときは、週5日で10時間勤務(2時間残業)で行う
  • 炎上間近のときは、週6日で10時間勤務、土曜出勤日曜日は休みで行う

こうすうことによって、週40時間が、少し忙しめのときは2割増し、炎上ぎりぎりのときは1.5倍のときの開発時間を確保できます。休日を外さないのは、継続的に1か月以上働けるのはこれが限界だからです。その後に燃え尽きてしまいますからね。

このように、月160時間でプロジェクトの期間見積をしておくと、機能追加や見込み違いなどでも同じメンバーで1.5倍までは開発期間を確保できます。

逆に言えば、1.5倍を超える場合は、人員を確保しないと駄目≒予算が増える、ということです。本来ならば残業代が増えるのですが、それは会社によりけりなので。

また、あらかじめ Google の 20% ルールを採用しておいて、繁忙期には20%ルールを外してもよいというルールにしておきます。フリーランスでだらだらとやっている身ですが、本の執筆や開発が混ざったときはこのパターンを採用していますね。お試しあれ。

カテゴリー: 開発 | 週40時間勤務と20%ルールでプロジェクト納期を厳守する はコメントを受け付けていません

計画駆動とSHIROBAKOの進行係の関係

SHIROBAKO http://shirobako-anime.com/ は、アニメ制作会社のアニメな訳ですが、主人公の宮森あおいはアニメーションを描けません。テレビアニメーションを作るのに、絵を描くアニメーターの存在は不可避なわけですが(キャラデザとか作監とか監督とか企画とかも)、このなかで制作会社として「進行」係に焦点を当てたアニメ作品です。

以前、ブログで書いた後に http://www.moonmile.net/blog/archives/10049 に続きを書こうと思ったら、京都アニメーションに放火事件があって、その後を続ける気分にならなかったのですが、ちょっと思ったことを書き下しておきます。

by SHIROBAKO

アニメーション制作と計画駆動の類似点

SHIROBAKO はアニメ制作会社の話なので、ある意味でソフトウェア開発の計画駆動(ウォーターフォール開発)に近いところがあります。いわば、

  • 最終締め切り(納品)が明確に決められていて、動かせない
  • プロジェクト進行の手順がほぼ決定されていて、前後しない
  • 前のタスクが遅れると後ろのタスクに影響がでる

というところです。一般的なアジャイル開発では、予算の変更やリリーススケジュールの変更が可能であるのですが(ここができないと、あまり「アジャイル開発」とは言えません)、従来型の計画駆動の場合はリリース日が決まっていたり、途中のマイルストーンが決まっていたりします。例えば、アプリのリリース日がプレスリリースされている場合は、その納品日を動かすことはまずできません。システム運用の切り替えや、引継ぎなどの関係、法律の関係など、納品日が絶対であるソフトウェア開発は結構あります。
この場合、余裕のある納品日と予算とスケジュールを立てればよいのですが、ソフトウェア開発において不可避な状態に陥ると、この「納品日」を守るが、かなり大変になりいわばデスマーチ化します。

ソフトウェア開発において、設計→製造(コーディング)→試験の順番は、XPやスクラムなどのアジャイル開発、イテレーション開発などによって一定ではないことが多くなってきました。しかし、多数の会社が参加していたり、ハードウェアとソフトウェアの両輪が回っていたりすると、設計→製造の順番が変えられないことが多くあります。この場合、従来型の計画駆動を踏襲して、概要設計や外部設計を重視したりします。

SHIROBAKOのアニメーション制作においても監督のシナリオ作りからラフ絵、作監、動画、撮影などの順番は変わりません。さすがにこれを前後することはできません。この順番をうまく回していくために、アニメ制作会社ではマネジメント業務として「進行」係が割り当てられます。昔のアニメーションを見ると「進行」の担当者が必ずいます。

進行係が何をするかと言うと、上司的な「マネージャー」役をやるわけではありません。SHIROBAKOを見るとわかるのですが、主に雑用係っぽいところが多いです。しかし、制作会社の立場から監督、作監、動画などのフリーランスあるいは外注を含めて、とりまとめをしていく重要な役割です。ある意味で、各フリーランスや外注の会社がきちんと〆切を守るのであれば、進行係の仕事は必要なく、非常に暇ですよね。でも、そんなことはありえません。確率的にゼロに等しいのです。なので、不確定要素を含みつつ、うまくアニメーションが出来る上がるまで(放映日まで)なんとかするのが「進行係」の重要な仕事です。

ソフトウェア開発においても、マネージメントという業務があり、権限の強いマネージャーという組織化をすることもあるのですが、果たしてそれがうまく廻るかどうかはわかりません。従来のウォーターフォール開発からアジャイル開発に移行しつつあるように、単なる工場的な上司部下的な分け方でソフトウェア開発ができなくなっています。そういう場所でマネージャーの行う「マネジメント」とい業務は何をするべきなのか?という疑問がある常々おこるのですが、これの答えのひとつが「進行係」です。

進行係はプレイングマネージャーではない

SHIROBAKO の主人公宮森あおいは、絵が描けません。高校時代のアニメ部?の仲間はアニメーターや声優になるわけですが、宮森だけは直接アニメーションを作る作業ができるという訳ではないのです。しかし、本作品で重要なのは、実際にアニメーションを作る(ソフトウェア開発ではコーディングをする、設計をするなど)訳ではなく、全くそれ以外の場所でもアニメ制作には重要である、という処に焦点があります。

ソフトウェア開発における「プレイングマネージャー」という役割は、アジャイル開発スクラムのリーダーやスクラムマスターっぽい位置に属します。開発グループにおいて、ある程度コードが書ける=尊敬に値するというカリスマ性は一種重要なものを含んでいるのですが、これがプロジェクト成功において必須条件という訳ではありません。プロジェクト成功の基準を

  • スケジュール通りに納品すること
  • 予算内で終わらせること
  • プロジェクトメンバが脱落しないこと(退職など)

のように、ヒト・モノ・カネの軸で言えば、プロジェクトマネージャのカリスマ性はあまり必要ではないことがわかります。たまに、三番目を外して「プロジェクトを成功」させるマネージャが多いのですが、会社としては継続し得ないプロジェクトメンバ=社員を作ってしまうことは大きな損失なので、プロジェクトが成功したとは言えないでしょう。

ゆえに、ソフトウェア開発におけるマネジメント業務を「進行」という雑用っぽいところまでに落とし込んでいくと、プレイング=コード書ける、必要がないことがわかります。また、ドラッガーがいう処のマネジメント=あれこれ調節する役割も必要ありません。

進行係においては、進行係自身には権限がないのですが、それぞれの権限のある人(監督や作監、動画など)のパフォーマンスが十分に保たれるように動きます。ソフトウェア開発で言えば、プログラマ、営業、テスター、システムエンジニアのように別々の特殊技能を持っている人のパフォーマンスをあげるように調節する役柄です。これを「マネジメント」と言うのですが、一般的な「マネージャー」と区別するために「進行係」という名称を流用します。

進行係は何に注力するのか?

計画駆動における進行係の存在は、現在のところ(といはいえ既に思いついてから20年以上たっているけど)目立った形で見えるわけではありません。本来ならば、マネジメント業務のひとつとして「進行」の役割を演じればいいのですが、

  • 顧客交渉の強い立場としてのマネジメント
  • 進行を円滑するするための下支えのマネジメント

とが、なかなか共有できるとは思えません。とは言え、私の個人的な経験では2名ほど上記の2つが同時実行できるマネージャを知ってはいるのですが、どうも個人的な素質の部分が大きく、体系化できそうにないです。

なので、計画駆動においては、強い立場のマネジメントを従来型のマネージャーが担い、プロジェクト進行を滞りなく進めるマネジメントを進行係として別に割りあてるのがよいでしょう。
とは、いえ、SHIROBAKO のような大所帯(アニメーション制作には100人以上に人がかかわっています)とは違い、昨今のソフトウェア開発のような小規模(5人程度)のプロジェクトにおいて「進行係」を別に立てることができるかといえば、そうはならないでしょう。30人規模のプロジェクトならば、進行係がいたほうがいいかなと思うのですが、現実問題として、独立したひとに「進行」を任せるのは小規模の開発プロジェクトでは無理です。なので、アジャイル開発のように混在した形になりがちです。

締め切り厳守するための進行の役割

となると、ソフトウェア開発において「進行」の役割は、単一の人に割り当てるのは無理そうです。適用範囲が狭すぎるので、あまり現実的ではありません。

ただし、先に書いたようにアジャイル開発が締め切りを動かすことを前提としているように(場合によっては、スクラム開発で強引に締め切りを守る方法もありますが、そこは「機能」を調節しますよね)、計画駆動のように締め切りを前提とした開発スタイルにもメリットは大きいのです。

アジャイル開発において、顧客の要求の変動により仕事量が上下することを前提としていますが、ソフトウェア開発においてはある程度までこの変動を抑えられることがあります。いわば、商品開発的な受託開発や提案型の受託開発です。この場合は、アジャイル開発が使われることが多いのですが(それを売りにする場合もあるし)、敢えて変動要素を少なくするようにすれば、進行的な役割を据えて無理なく締め切りを迎えることが可能になるでしょう。

そこは予測、と変動を許容するプロジェクトバッファという形になるので、これはアジャイル開発自身にも使われているところです。

  • バーンダウンチャートとアーンドバリューの組みあわせ
  • 工事進行基準の応用
  • チケット駆動におけるトータルチケットの予測値

古くは CMMI のレベル4と5の中間あたりですね。CMMI 自体が消え去ってしまっていますが、参考にできるところは多いです。あと、稲森会長の「アメーバ経営」を参考に。

参考

アニメ制作について|TVアニメ「SHIROBAKO」公式サイト http://shirobako-anime.com/about.html

カテゴリー: 開発 | 計画駆動とSHIROBAKOの進行係の関係 はコメントを受け付けていません

.NET MAUI から Firebase を利用する

.NET MAUI が Visual Studio 2022 の正式版にアップデートされたので、適度にツールを作ってみます。のテストです。ちょっと、手元で入用な Firebase 接続確認のアプリを作る必要があってので、.NET MAUI を使ってアプリを作っていきます。

と、結構すんなりいく筈だったのですが、意外と難関だったので、備忘録として残しておきます。

Visual Studio 2022 で .NET MAUI プロジェクトを作る

ツールの対象として iPhone アプリになるので、iOS のみを対象としています。

.NET MAUI は Xamarin.Forms とは異なり、

  • コードが .NET 6 ベースになる(内部的には mono だけど、NuGet のライブラリが .net 6 で揃えられる)
  • 各プラットフォームは #if で切り替える
  • 各プラットフォームはひとつのプロジェクトになる

となります。

ちょっと、ややこしいのは各プラットフォーム(iOS, Android、MacCtalyst、UWP)のコードがひとまとまりになって、ビルドをするたびに4つのプラットフォームを全部ビルドします。なので、

  • ビルドに少々時間が掛かる
  • 別プラットフォームでビルドできないコードを含めてしまうと、エラーがうっとおしい。

という状態に陥ります。

たとえば、モバイルアプリの場合は、iOS用とAndroid用しかいらないわけで Windows(UWP)のほうビルドエラーがでると面倒なことになる、のですが、どうするのかは微妙なところです。多分、*.csproj で TargetFrameworks を絞ればいいと思うのですが。

ひとまず、ビルドして実機で動かす

実は、「逆引き大全2022」と「.NET6本」の .NET MAUI の章は Visual Studio 2022 のプレビュー版を使っています。概ね今回の正式版と変わらないのですが、プレビュー版ではiOS シミュレータの動作が不安定であったり、iPhone 実機で動作できなかったりしました。

なので、さっそく実機で動かしておきます。

画面のほうは、実機ではなくシミュレータのものですが、Visual Studio 2022 からデバッグモードで問題なく動いています。

iOS 版を作るときは、Mac とのペアリング等もろもろの設定が必要のですが、これは Xamarin.Forms のときと同じです。

.NET MAUI と Xamarin.iOS が共存できない?

.NET MAUI プロジェクトを作るときの注意点ですが、Mac に .net 6 ベースの mono(かな?)を入れてしまうらしく、従来の Xamarin.iOS との共存ができません。正確には、

  • Windows 上の Visual Studio から .NET MAUI のプロジェクトを作成し、Mac に接続する
  • Windows 上の Visual Studio から Xamarin.iOS のプロジェクトを作成、Mac に接続する

ことができません。mac 上の mono のバージョンが異なるようで、Xamarin.iOS pうろジェクトが動きません。

ただし、

  • Windows 上の Visual Studio から .NET MAUI のプロジェクトを作成し、Mac に接続する
  • Mac 上の Visual Studio から Xamarin.iOS のプロジェクトを作成し、Mac 上で動かす。

ことは可能です。プロジェクトに設定されている mono のバージョン違いなのかもしれません。

Firebase に接続するための NuGet をインストール

Xamarin.Forms のときにどうだったか忘れてしまったのですが、Xamarin から Firebase に接続するための NuGet パッケージは、Android と iOS では別々のものが提供されています。コマンド自体は HTTP 接続だろうから、共通なのでは?と思ったけど暫くハマりました。

iOS 版の NuGet パッケージは以下になります。

https://github.com/xamarin/GoogleApisForiOSComponents

  • Xamarin.Firebase.iOS.Core
  • Xamarin.Firebase.iOS.CloudFirestore
  • Xamarin.Firebase.iOS.Auth

の3つ入れておけば大丈夫です。

ちなみに Android 版のほうは「Xamarin.Firebase.Common」のように、「iOS」がないものを使うので注意が必要です。これ、なんらかの形でまとめてくれませんかね。Windows/UWPの場合は不明です。。。

サンプルコードは

https://github.com/xamarin/GoogleApisForiOSComponents/tree/main/samples/Firebase/CloudFirestore/CloudFirestoreSample

にあるので、参考にしてください。

Xamarin.Firebase.iOS.CloudFirestore パッケージを入れた後でビルドに失敗する

Xamarin.Firebase.iOS.CloudFirestore パッケージを NuGet で入れると、.nuget フォルダーにパッケージがダウンロードされます。が、これがビルド時に失敗してしまいます。

パッケージには *.h ファイルが含まれてい、なんらかの形で参照をしているのですが、Windows の PATH の制限があって、これがエラーになってしまうのです。

ひとつの解決先は

https://github.com/xamarin/GoogleApisForiOSComponents/issues/555

にある通り、環境変数 NUGET_PACKAGES を設定して「c:\Nugets」のように PATH が短くなるように工夫します。

ですが、これは根本的な解決策にはなりません。おそらく Visual Studio 2022 がビルドに使っているターミナルが cmd ベースなのが問題でしょう。試しに、Powershell を立ち上げて、独自に「dotnet build」とすると無事に長い PATH でエラーになる問題は解決されます。

Xamarin.Firebase.iOS.* パッケージが設定しようといている *.h ファイルは最初の1回だけで、あとはキャッシュが使われるようなので、NuGet パッケージの設置した後に一回だけ「dotnet build」あるいは「dotnet restore」しておくとよいです。

ちなみ、Visual Studio 2022 上でビルドエラーになる現象は、何度か発生します(不定期です)。この場合も、Powershell 上で dotnet build すると直ります。

Firebase に登録するコードを書く

  • Firebase.Core.App.Configure で初期化
  • Document を作成して、SetData で登録

すれば OK です。この部分は、Xamarin.iOS の頃と同じ筈なのですが、Xamarin.iOS と Firebase の組み合わせを使っている人が少なくて、探すのに苦労しました。Android のほうはそれなりにあるので、大丈夫だと思います。

private void MainPage_Loaded(object sender, EventArgs e)
{
#if IOS
		Firebase.Core.App.Configure();
#endif
}
/// <summary>
/// Firebaseに接続
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnClickConnect(object sender, EventArgs e)
{
#if IOS
		message.Text = "登録開始";
        var store = Firebase.CloudFirestore.Firestore.SharedInstance;
		var coll = store.GetCollection("contacts");
		var doc = coll.CreateDocument();
        var dic = new Dictionary<object, object>();
        dic.Add("device_id", "0000");
        dic.Add("name", "test");
		var time = Firebase.CloudFirestore.Timestamp.Create(
			(long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds, 0);
        dic.Add("update_at", time);
        doc.SetData(dic);
		message.Text = "登録しました " + DateTime.Now.ToString();
#endif
}

#if で囲ってあるのは、コードビハイドの MainPage.xaml.cs で記述しているので、iOS しかビルドが通らないためです。この部分は適宜 Android/iOS 共通のクラスを作っておいて、内部で #if で分けるのがベターです。Android と iOS で名前空間が違うので、Xamarin.Forms のときのようにインターフェースを駆使するよりも、#if でビルド時に切り分けてしまったほうが楽です。

時刻は、Firebase 側で Timestamp 型を扱うため、Firebase.CloudFirestore.Timestamp に変換して使います。

GoogleService-Info.plist を配置する

Firebase からダウンロードした「GoogleService-Info.plist」をプロジェクト内に配置させます。

これが苦労して2時間ぐらいかかりました。

GoogleService-Info.plist は、プロジェクトのルート(Plaftforms/iOS の下ではない!)において、手作業で、BundleResource を記述します。

	<ItemGroup>
		<BundleResource Include="GoogleService-Info.plist" Condition="Exists('GoogleService-Info.plist')" />
	</ItemGroup>

Visual Studio 2022 のプロパティでは、「BundleResource」を設定できません。日本語の「埋め込みリソース」は EmbeddedResource に変換されているので、多分 .NET MAUI の *.target あたりのバグじゃないかなと。

Android の場合は「GoogleService-Info.json」なので、拡張子が違ってダブらないのですが、Windows 版はどうだったかな、と。ファイル名がダブル場合は困ることになるので、今後問題になりそうです。

ただし、コードでファイル名を指定したりパラメーターを独自に設定したりする方法があります。

Cloud Firestore のルールを設定しておく

動作させた最初では、Firebase に登録ができなくて、.NET MAUI & Firebase の不具合では?と悩んでいたのですが、ルールがきつい設定になっていました。

ひとまず、動作確認をしたいときは、以下のようにルールを緩く設定しておくと便利です。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /contacts/{id} {
      allow read, write
    }
  }
}

実運用になると、request.auth でユーザー権限を調べて uid のチェックをしたほうがいいですよね。実際のアプリのほうはそうなっています。

実行する

アプリを実行するとこんな感じ。

Firebase のドキュメントに無事データが入っています。

カテゴリー: 開発 | .NET MAUI から Firebase を利用する はコメントを受け付けていません

.NET Framework 4.7 から .NET6 への移行ポイント

Windows 上の .NET Framework から .NET 6 へ移行するときに、障害となりそうなポイントを挙げておきます。ちょっとした Windows フォームや WPF アプリならばすんなり以降できるのですが、それなりの大きさの C/S 方式で Entity Framework を使っていると(使っているんだが)、移行時に躓きがありそうです。

移行対象

  • WPF アプリで約70画面ある。
  • テーブル数は約20テーブルある。
  • MVVMパターンを使い、View と ViewModel を分離させている
  • Model は .NET Framework の Entity Framework を使っている。コードファースト以前の、*.edmx ファイルを使う方式(SQL Server のテーブルからコードを自動生成するので当時は便利だった)
  • テーブル検索はほぼ LINQ を使っているが、移行前の複雑な SQL を使うため SqlQuery で直接呼び出している部分がる。
  • View にバインドさせるため、Entity クラス(*.edmx による自動生成)に、適宜プロパティをはやしている。
  • Excel のコントロールを埋め込むため、WindowsFormsHost を使っている
  • Excel の帳票をつかうため、Microsoft.Office.Interop.Excel を使っている

ステップ数は数えてはいませんが、大体1万行を超える位です。もともと、ACCESS 版を C# 版に書き直したもので、これを試しに .NET6 に移そうと試しています。

全体の構造

.NET Framework の WPF を .NET6 の WPF に移行するので、全体のフォルダー構造は変えません。 中のコード(特にロジック)には手をいれたくないので、namespace はそのまま使うようにします。

Entity Framework の移行

.NET Framework の System.Data.Entity.DbContext を .NET6 の Microsoft.EntityFrameworkCore. DbContext に移行します。同じ「 DbContext 」となっていますが、中身は別物です。.NET6 のほうは、NuGet で “Microsoft.EntityFrameworkCore.SqlServer” をインストールします。

.NET Framework の EF とは違い、*.edmx のようなテーブルの構造を定義したファイルがありません。コードファースト的にクラスを定義しておくか、データベースファースト的に SQL Server から dotnet ef コマンドを使いテーブルクラスを生成させます。テーブルクラス = Entity クラスは、以前の .NET Framework と同じものを使うので(プロパティ名などが変わるとやっかい).NET Framework 版のものをコピーして Models フォルダに入れておきます。

DbSet クラスは、

  • .NET Framework では System.Data.Entity.DbSet
  • .NET6 では Microsoft.EntityFrameworkCore.DbSet

となり同じ「DbSet」という名前になっていますが、似て非なるものです。

SQL Server に接続する文字列は、.NET6の場合(EFCoreの場合)は OnConfiguring を override して記述します。場合によっては、複数の DbContext を切り替える必要があるので、外部から切り替えができるようにしておきます。

public static 会議室Entities CreateEnt(string dbname, string servername = "(local)")
{
    var builder = new SqlConnectionStringBuilder();
    builder.DataSource = servername;
    builder.InitialCatalog = dbname;
    builder.IntegratedSecurity = true;
    var cnstr = builder.ConnectionString;
    var context = new 会議室Entities();
    context.Database.SetConnectionString(cnstr);
    return context;
}

主キーを付加する

.NET Framework の EF が出力する Entity クラスは主キー(Key属性)がありません。このため、この Entity クラス .NET6 の EFCore で使うと実行エラーが発生します。検索時ではあっても、デフォルトでは主キーが必要になっているためです。.NET Framework の場合はでは、*.edmx ファイルに主キーなどのテーブル構造の情報がはいっているため、Entity ファイルには Key属性等がついていません。

public partial class 会社基本情報
{
    public int id { get; set; }
    public string 会社名 { get; set; }
    public string 肩書き { get; set; }
    public string TEL { get; set; }
    public string FAX { get; set; }
    public string 郵便番号 { get; set; }
    public string 住所1 { get; set; }
    public string 住所2 { get; set; }
    public string 期名前 { get; set; }
    public Nullable<double> 消費税 { get; set; }
    public Nullable<int> 端数処理コード { get; set; }
    public Nullable<int> 年 { get; set; }
    public Nullable<System.DateTime> 期首日 { get; set; }
    public Nullable<System.DateTime> 期末日 { get; set; }
    public string 振込先A { get; set; }
    public string 振込先B { get; set; }
    public string FA { get; set; }
    public string FB { get; set; }
    public string GA { get; set; }
    public string GB { get; set; }
    public string HA { get; set; }
    public string HB { get; set; }
}

ここの id 部分に Key属性をつけてもよいのですが、このファイル自体はさわりたくないので、DbContext の OnModelCreating メソッドで調節します。

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<ホームページ>().HasKey(x => x.ID);
        modelBuilder.Entity<確認>().HasKey(x => x.日付);
        modelBuilder.Entity<期首>().HasKey(x => x.ID);
        modelBuilder.Entity<県>().HasKey(x => x.ID);
        modelBuilder.Entity<顧客>().HasKey(x => x.ID);
        modelBuilder.Entity<顧客SUB>().HasKey(x => x.ID);
        modelBuilder.Entity<予約>().HasKey(x => x.ID);
        modelBuilder.Entity<予約SUB>().HasKey(x => x.ID);
        modelBuilder.Entity<予約確認>().HasKey(x => x.ID);
        modelBuilder.Entity<予約状況>().HasKey(x => x.ID);
        modelBuilder.Entity<会社基本情報>().HasKey(x => x.id);

まめに HasKey で主キーを設定してやれば ok です。

生 SQL を呼び出したときの戻り値のクラスでは、主キーがないことを設定する

実テーブルを参照する場合には主キーがあるのですが、生 SQL を使って検索結果を返すときには主キーはありません。なので、.NET6 の EFCore では、HasNoKey を使って明示的に設定しないといけません。

全て LINQ で済めばよいのですが、複雑な join が発生する場合、生 SQL を使ったようが良い場合があります。例えば、以下のような SQL を LINQ に書き直すのはかなり困難ですし、移行時に間違いが発生します。なので、そのまま SQL を使います。

        var SQL = $@"
select
予約.ID,
顧客.会社名,
予約.日付,
部屋.名称 as 会議室,
予約状況.開始時刻 as 開始,
予約状況.終了時刻 as 終了,
顧客sub.部署名 as 部署名,
顧客sub.担当者A as 担当者,
顧客sub.TEL as TEL,
顧客sub.FAX as FAX,
予約.予約 as 状態,
予約.契約日 as 契約日,
予約.確認票最終処理日 as 確認日,
請求.日付 as 請求日,
X.利用料,
isnull(入金.入金額,0) as 入金額,
予約.備考B as 注意事項,
予約.案内名称 as 案内板名称
from 
( 
select
予約.ID as ID,
cast(ISNULL(sum(予約sub.単価*予約sub.数量),0) as money) as 利用料
from 予約
left join 予約sub on 予約sub.予約ID = 予約.id
where 予約.顧客id = {顧客ID}
group by 
予約.ID 
) X
inner join 予約 on 予約.id = X.ID
inner join 顧客 on 顧客.ID = 予約.顧客ID
inner join 顧客sub on 顧客sub.ID = 予約.顧客SUBID
left join 請求 on 請求.id = 予約.請NO
left join 入金 on 入金.請求ID = 請求.ID
inner join 部屋 on 部屋.id = 予約.会議室
inner join 予約状況 on 予約状況.予約ID = 予約.ID
order by 予約.日付 desc 
";
this.Items = App.ent.Database.SqlQuery<結果>(SQL).ToList();

この時、戻り値として使っている「結果」クラスは、.NET Framework の場合は適当に値クラスを作って検索結果を受け取ればいいのです(いちいち値をクラスを作るのが面倒ではあるのですが)。

しかし、.NET6 の EFCore には Database.SqlQuery に相当するものがなく、DbSet<T>.FromSqlRaw を使うことになっています。使い方は、以下のようにいったん DbContext に「結果」コレクションをはやすことになります。

            var SQL = $@"
select
...
";
this.Items = App.ent.結果.FromSqlRaw(SQL).ToList();

これに伴い、DbContext クラスには、戻り値のクラスも DbSet<T> で定義しないといけません。

        // 戻り値用
        public virtual DbSet<ViewModels.VM請求一覧.請求一覧> 請求一覧 { get; set; }
        public virtual DbSet<ViewModels.VM顧客履歴.結果> 結果 { get; set; }
        public virtual DbSet<ViewModels.VM予約一覧.Data> 予約一覧Data { get; set; }
        public virtual DbSet<ViewModels.VM予約詳細入力.引合> 予約詳細入力引合 { get; set; }
        public virtual DbSet<ViewModels.VM属性別集計.属性> 属性別集計属性 { get; set; }
        public virtual DbSet<ViewModels.VM年間グラフ.年間売上> 年間売上 { get; set; }
        public virtual DbSet<ViewModels.VM月別売上.月別売上> 月別売上 { get; set; }
        public virtual DbSet<ViewModels.VM請求集計.請求> 請求集計請求 { get; set; }
        public virtual DbSet<ViewModels.VM過去予約状況.結果> 過去予約状況結果 { get; set; }
        public virtual DbSet<ViewModels.VM顧客別利用状況集計.結果> 顧客別利用状況集計結果 { get; set; }
        public virtual DbSet<ViewModels.VM顧客利用回数一覧.利用回数> 顧客利用回数一覧利用回数 { get; set; }
        public virtual DbSet<ViewModels.VM顧客利用状況.利用明細> 顧客利用状況利用明細 { get; set; }

実は、戻り値クラスはそれぞれの ViewModel クラス内でしか使わないので private になっているのですが、.NET6 の場合には DbContext から参照できるように public(あるいはinternal)の変更が必要になります。はっきり言って、この FromSqlRaw の仕組みはよくないです。もともと内部クラスになるので、名前の重複が激しくなっています。

この戻り値専用のクラスは、主キーがありません。これを明示的に示すために OnModelCreating 内で HasNoKey を指定します。

            modelBuilder.Entity<ViewModels.VM請求一覧.請求一覧>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM顧客履歴.結果>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM予約一覧.Data>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM予約詳細入力.引合>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM属性別集計.属性>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM年間グラフ.年間売上>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM月別売上.月別売上>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM請求集計.請求>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM過去予約状況.結果>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM顧客別利用状況集計.結果>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM顧客利用回数一覧.利用回数>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM顧客利用状況.利用明細>().HasNoKey();

Entity クラスに NoKey 属性のようなものがあればよいのですが、どうも無いみたいですね。

データベース上のカラム名と一致させる

.NET6 の EFCore では、データベース上もカラム名と Entity クラスのカラム名が完全に一定していないといけません。よって、次のようなデータベース上では、正式な名前の場合、ちょっと困ることがおこります。

これは前回の移植上、予約テーブルに「予約」という名前のカラムがついています。

実は、Entity Framework では、テーブル名とカラム名が同じのは使えません。以前から問題だと思っているのですが、テーブル名もカラム名も同じ名前空間で混在させてしまっているからなのです。

なので、.NET Framework の EF では、以下のように「予約1」という名前が勝手に振られます。

    public partial class 予約
    {
        public int ID { get; set; }
        public int 顧客ID { get; set; }
        public int 顧客SUBID { get; set; }
        public Nullable<int> 予約者 { get; set; }
        public string 予約会社 { get; set; }
        public string 予約担当 { get; set; }
        public string 予約TEL { get; set; }
        public int 担当者ID { get; set; }
        public Nullable<System.DateTime> 日付 { get; set; }
        public Nullable<System.DateTime> 記録日 { get; set; }
        public Nullable<System.DateTime> 契約日 { get; set; }
        public Nullable<System.DateTime> M期限 { get; set; }
        public Nullable<System.DateTime> K期限 { get; set; }
        public Nullable<int> 予約1 { get; set; }
        public Nullable<int> 会議室 { get; set; }
        public Nullable<int> TF { get; set; }
        public Nullable<int> TE { get; set; }

ところが、.NET6 の EFCore では、データベースのカラム名と Entity クラスのプロパティ名は同一であるという前提があるので、以下のように Column 属性で変更しておきます。

    public partial class 予約
    {
        public int ID { get; set; }
        public int 顧客ID { get; set; }
        public int 顧客SUBID { get; set; }
        public Nullable<int> 予約者 { get; set; }
        public string 予約会社 { get; set; }
        public string 予約担当 { get; set; }
        public string 予約TEL { get; set; }
        public int 担当者ID { get; set; }
        public Nullable<System.DateTime> 日付 { get; set; }
        public Nullable<System.DateTime> 記録日 { get; set; }
        public Nullable<System.DateTime> 契約日 { get; set; }
        public Nullable<System.DateTime> M期限 { get; set; }
        public Nullable<System.DateTime> K期限 { get; set; }
        [Column("予約")]
        public Nullable<int> 予約1 { get; set; }
        public Nullable<int> 会議室 { get; set; }

この部分は、実は「予約」プロパティに変更してもよいのですが、既に各種のコードから「予約1」で参照しているので、参照先のコードは変更したくありません。よって、Column 属性でのがれておきます。

拡張したプロパティを DbSet に無視させる

.NET Framework の Entity クラスを MVVM パターンで View にバインドさせる場合、表示上フォーマットを変更させることがよくあります。Converter を作るのもよいのですが、どうせ partial になっているので、適当なプロパティを作って拡張させておくと便利、だったのです。

    public partial class 単発アラーム
    {
        public int ID { get; set; }
        public Nullable<System.DateTime> 日時 { get; set; }
        public string 対象種別 { get; set; }
        public string 対象名 { get; set; }
        public string 表示内容 { get; set; }
        public System.DateTime CreateAt { get; set; }
        public System.DateTime UpdateAt { get; set; }
    }

このような自動生成された Entity クラスとは別に、patial で拡張しておきます。

    /// <summary>
    /// 日時を年月日と時分に分ける拡張
    /// </summary>
    public partial class 単発アラーム
    {
        public Nullable<System.DateTime> 日時_年月日
        {
            get { return this.日時; }
            set
            {
                if ( this.日時.HasValue )
                {
                    DateTime dt = new DateTime(
                        value.Value.Year,
                        value.Value.Month,
                        value.Value.Day,
                        日時.Value.Hour,
                        日時.Value.Minute,
                        0);
                    this.日時 = dt;
                }
            }
        }
        public Nullable<System.DateTime> 日時_時分
        {
            get { return this.日時; }
            set
            {
                if (this.日時.HasValue)
                {
                    DateTime dt = new DateTime(
                        日時.Value.Year,
                        日時.Value.Month,
                        日時.Value.Day,
                        value.Value.Hour,
                        value.Value.Minute,
                        0);
                    this.日時 = dt;
                }
            }
        }
    }

ここで .NET6 への移植時に問題が発生します。

.NET Framework の EF では、テーブル構造が *.edmx ファイルに分離されているので、拡張した日時_年月日プロパティや日時_時分プロパティは更新時に無視されるのですが、.NET6 の EFCoreでは更新対象がEntityクラスの全プロパティとなるため、この拡張したプロパティを「無視」させるようにしなければなりません。

DbContext の OnModelCreating 内で、

            modelBuilder.Entity<単発アラーム>()
                .Ignore("日時_年月日")
                .Ignore("日時_時分");
            modelBuilder.Entity<周期アラーム>()
                .Ignore("周期月一週")
                .Ignore("周期毎年")
                .Ignore("周期毎月")
                .Ignore("周期毎週")
                .Ignore("周期月一曜日")
                ;

のように、拡張したプロパティを ignore するようにしていきます。この部分は、拡張したプロパティのほうに Ignore属性が付けられればよいのですが、無いようです。不便ですね。

問題はこのエラーの発生は、コンパイル時ではなく実行時にしか発生しないので、いちいち確かめないといけません。元の Entity クラスを拡張していると結構やっかいな問題です。

WindowsFormsHost を使う

.NET Framework の WPF アプリケーションでは WindowsFormsHost を使って、Windows フォームの各種コントロールを埋め込むことができます。

これは客先の「カレンダーのフォーマット/色合いを従来のものと同じにしてほしい」という要望を実現したものです。従来は ACCESS のカレンダーを使っているので、WPF カレンダーのものは使えません。

しかたがないので、ユーザーコントロールを作って WindowsFormsHost で埋め込みます。ユーザーコントロールにしたのは、あちこちの画面でこのカレンダーが出てくるので共通化するためです。

<Canvas Grid.Row="1" Grid.Column="0" x:Name="cv">
    <WindowsFormsHost FontSize="20" x:Name="host" >
        <wf:MonthCalendar 
            x:Name="cal" CalendarDimensions="3,1" DateSelected="MonthCalendar_DateSelected" />
    </WindowsFormsHost>
</Canvas>

実は、.NET6 では WindowsFormsHost の部分が使えなくて暫く悩んでいたのですが、

<UseWindowsForms>true</UseWindowsForms>

プロジェクトファイルに UseWindowsForms を設定して解決します。おそらく、.NET6 の WPF アプリケーションでツールボックスから WindowsFormsHost をドロップすると自動的に、UseWindowsForms が true になるはずです。

office.dll への参照を追加する

かなり悩んだのが次のところです。

MsoTriState が存在しないというエラーがでています。これは Excel の Shape を設定している部分なのですが、Excel COM を参照させてはいても、このエラーは取り除けません。おそらく Excel/Word に共通化されている Shape クラスは「office, Version=15.0.0.0」にあるようです。

さて、 「office, Version=15.0.0.0」 は一体どこにあるのでしょうか?

実は、.NET Framework のプロジェクトを見ると「アセンブリ」のタブのほうに「office 15.0.0.0」を参照しているところがあります。これは Office の COM ではなくて、.NET Framework が提供しているアセンブリのほうなんですね。

.NET6 では各種のアセンブリを個別で参照することはできない(自動的にアセンブリ参照が解決されるため)ので、明示的に office.dll を参照させます。

プロジェクトファイルに以下を記述しても ok です。

  <ItemGroup>
    <Reference Include="office">
      <HintPath>c:\Program Files (x86)\Microsoft Visual Studio\Shared\Visual Studio Tools for Office\PIA\Office15\Office.dll</HintPath>
    </Reference>
  </ItemGroup>

その他

その他 Excel COM の参照や Newtonsoft.Json、ClosedXML の参照は問題なく動いています。

EFCore の拡張プロパティの問題は、実際に動かしてみないとエラーが発生しないのでもう少し対処が必要です。

作業時間はおおむね1日がかりなので、移行としては十分可能なレベルですが、問題は FromSqlRaw への書き換えですかね。外部結合を使う場合、LINQ ではかなり手間なので生 SQL で組んでしまうのですが、プロジェクトによって数が多いと大変かと思います。

.NET6 にしておくと何が便利かと言うと TargetFramework を「net6.0-windows10.0.17763.0」にしておいて Windows Runtime(UWP や Windows App SDKの機能)が手軽に使えるようになります。まあ、業務用のデスクトップアプリなので、公開とかしないわけでそれほど利用価値があるわけではないのですが。ふりがな機能とか通知機能、連絡帳などが WinRT のほうに入っていたりします。

カテゴリー: 開発 | .NET Framework 4.7 から .NET6 への移行ポイント はコメントを受け付けていません

Exposure Notifications/COCOAのビーコンを収集して解析する

Google/Apple の提供する Exposure Notifications では BLE でビーコンを発信しています。内部的には 32 バイトなデータを Advertising/Broadcasting ってことになります。

コード自体は、openCACAO/CacaoBeacon: https://github.com/openCACAO/CacaoBeacon でビーコンの受信&収集コードを公開しています。CacaoBeaconMonitor が Android アプリです。iOS 版はありません。ENs では、ビーコンにある ID を受信するのですが、iOS の場合はこれが OS 側で塞がれているためです。なので、ビーコンのモニタリングとしては Android あるいは Windows を使います。

ノートパソコンを持ってうろうろするのは大変なので(定点観測ならばノートPCでも十分ですが)、Android のスマホを使います。

感染が拡大したので蒐集してみる

コード作ったのが4か月前で、去年の秋には東京都の感染者がぐんぐん減っている時期で、実験的なデータがとりずらい状態でした。で、まあ、とりあえず放置状態にしてあったのですが、第6波の勢いは尋常ではなく、現時点で東京都では2万/日をカウントしています。実に去年の秋の1000倍近い数字になってしまったわけですが。

COCOA自体の稼働率は、おそらく人口比で3割を超えています。通勤のような社会人に限っていれば、半分以上はあるっぽいです。 CacaoBeaconMonitor の場合は、COCOA から発信するビーコンを受けて RPI を保持していく Google/Apple の Exposure Notifications のクローンのような動作をさせているので、多分正確に収集ができます。

陽性者とのマッチングは別途、probetek で zip をダウンロードしてきて、SQL Server 上でクエリを書いています。

実測データ

2022年2月3日に、池袋を2時間ほど徘徊したデータ

https://1drv.ms/u/s!AmXmBbuizQkXgrlPo98iECzyoSgy8w?e=nr7QM0

上記から SQLite のデータがダウンロードできます。東武東上線で池袋まで行って、東武デパートで食事をして、帰ってくるまでの2時間のデータになります。

収集データの解説

テーブル構造は、以下の通り

CREATE TABLE RPI (
    Id INTEGER NOT NULL CONSTRAINT PK_RPI PRIMARY KEY AUTOINCREMENT,
    Key BLOB NULL,
    Metadata BLOB NULL,
    StartTime TEXT NOT NULL,
    EndTime TEXT NOT NULL,
    RssiMin INTEGER NOT NULL,
    RssiMax INTEGER NOT NULL,
    MAC INTEGER NOT NULL
)

ビーコンの受信開始(StartTime)と受信終了(EndTime)で接触時間が計測できます。ただし、ビーコンの受信データだけだと、発信のほうが10分で切り替わるので10分以内しか判別できません。別途、陽性者のTEKをダウンロードしてきて、受信したRPIを照合させると10分以上の接触が確認できます。実際の ENs が「接触の可能性あり」で通知しているのはこの部分です。

電波強度(RssiMax)を見ると、どれだけ近接しているかがわかります。私自身 iPhone に COCOA を入れているので、ひとつだけはかなり近いデータになっています。それ以外は、電車やホームでのすれ違い、エスカレーターで並んでいたときと考えられます。データの収集は一瞬でも近づけばデータとして保持されるので、きちんと解析するときは、近接している時間(EndTime – StartTime)と、電波強度(RssiMax)を見るとよいでしょう。

実際に実験をしてみると、バスが通ったり、電車が通過したときでも同時接続数が40程度の爆上がりします。いわゆる一瞬のすれ違いでも計測されるということです。

COCOA が使う ENs の仕組みは完全に公開されているとは言えませんが、おおまかなところはコードで示されています。実際の照合のところは数式でしか示されていないのが残念なところですが、公式で言われている通り、電波強度と近接時間の両方をみて判断して1mと15分以上という値を割り出しています。

TEK データのダウンロード

COCOA を通して陽性登録された TEK のデータを probetek コマンドでダウンロードします。

dotnet run --all

2/4時点で、50万件のTEKがあります。TEK は日単位で変わるので、おおむね7で割ると登録数になります。おおむね7万件というところでしょう。このデータは全国なので、2,3割の陽性者が登録している計算になります。実際のところは、厚生労働省のページ https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/cocoa_00138.html で確認ができます。

この TEK データを SQL Server にアップロードします。

dotnet run --update-tek

TEK から、その日の 144個(10分単位)の RPI を生成して EXRPI テーブルに挿入しています。

と思ったのですが、データ量が 50万 x 144 で 7200万件になるので尋常ではありません。そのまま動かすとPCのメモリのデータベースもいっぱいになってしまうので、照合の部分は考え直しましょう。

実際の Google/Apple の ENs では C++ でバイナリ照合しているのでメモリは枯渇しません。どうやら照合用のツールを別途作る必要がありそうです。

カテゴリー: 開発 | Exposure Notifications/COCOAのビーコンを収集して解析する はコメントを受け付けていません