Life is Really Short, Have Your Life!!

ござ先輩の主に技術的なメモ

.NET Framework 4.8 → .NET 6 へ移行作業にトライ

死ぬほど重い腰を上げて、こちらにトライしてみた。そうはいっても、やることは単純だった。下記のUpgrade Assistantに沿って、Enterを叩きまくる簡単なお仕事。Enterを押す回数が多く、20回ぐらい押した。

dotnet.microsoft.com

やってみた所、あっさり.net6でビルドが出来た。PrintDocumentみたいに、Windows固有のAPIを書くと警告が出るようになった。 [SupportedOSPlatform("windows")]をつけまくるのは辛いので、Windowsしかサポートしないですっていうビルド設定みたいなやつが欲しい。

docs.microsoft.com

唯一困った点が、.NET FrameworkでしかサポートされていないPDFiumViewer が動かない。Unable to load DLL pdfium.dll というエラーが出る。.NET Frameworkでビルドした時はこのエラーが出なかったので、.NET6固有の問題のように思う。

PDFをダイレクト印刷したいがためにこれを使っている(それ以外の用途はない)が、PDFがインストールされているマシンなら、プロセスを起動して印刷できるっぽい。

stackoverflow.com

あっさり以下のコード出てきたけど、acrobat32.exe を直で叩いているためか、Acrobat Readerが立ち上がってしまう。また、このやり方はPDFを開くデフォルトアプリケーションがAdobe Readerでないとだめっぽい。Edgeがデフォルトの場合、PrintToオプションがなさそう。

 string dir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); //これはいらんかも
                Process p = new Process();
                p.StartInfo = new ProcessStartInfo()
                {
                    Arguments = "\"" + printer+ "\"",
                    Verb = "PrintTo",
                    FileName = path, //put the correct path here
                    WorkingDirectory = dir,
                    WindowStyle = ProcessWindowStyle.Hidden
                };
                p.Start();
                p.WaitForExit(1500);

なんて思ってたら、RawPrintなるものがあった!

www.nuget.org

Parallelsの仮想プリンタは印刷できず、Windowsの仮想PDFプリンタは印刷が実行された。なんだろう。コードすげー少ない。とりまfork。実機のプリンタで印刷できれば、なんでもええわ。

10年前に印刷要件の関係でWPFを選んだけど、全部Reactで焼き直したい気持ちが極めて強い... 1年ぐらいかけてまったりと。PDFのダイレクト印刷がReactでできれば、複合機・ドットインパクトプリンタはあまり変わらないはず? ページサイズの設定などが出来ないとやっぱWPFじゃんになるかもですが。

数値をバインドしてるTextBoxの入力値が空になるとエラーになる

現象の説明はこちらに詳しい。

Decimal? とかでnull許容型にしているデータをTextBoxへバインドすると、入力値を消して空にしたときに、赤枠でエラーが出る。何か入力したあとにバックスペースで空文字にすると、空文字として扱われてしまうため、NULLに変換できないためにそのようなことが起こる。 tawamuredays.blog.fc2.com

上記の記事ではConveter作っていたけど、実はBindingにそのようなためのプロパティがある。TargetNullValueだ。 blog.okazuki.jp

<TextBox Text="{Binding Path=NumberInput, UpdateSourceTrigger=PropertyChanged, TargetNullValue=''}" />

これで空文字のときはnullと同等の扱いとなる。めでたしめでたし。

Vercel on PlanetScale、大変興味があります

qiita.com

HerokuのMySQL AddonはRDSを$10から使えるのでその意味でコスパが良いけど、1時間に投げられるクエリの数であったり、一番安いプランはHDDだったりする。

GMOが運営しているConohaのDBサーバは$5でSSDで国内にあるので、コスパがすごい。同時接続数に制限があり、あんまりヘビーには使えない。コネクションプールを使うことが難しいので、リクエストを処理し終わったらコネクションを閉じる必要がある。スレッドベースで接続するからそこまでコスト高くないらしいけど。

RDS/Auroraはこれらのサービスに比べるとガツンと値段があがる。スケールするサービスを作るには最高なんだろうが、「年間数万レコード/同接多くて30」という業務システムのDBインフラとして「牛刀をもって鶏を割く」感が強く、数千円でホストOSの管理を一切しないで回せるインフラあるといいな〜ってずっと思ってた。

DBはPlanetScaleだとして、Heroku/Vercel/Render/Cloud Runあたりが候補になるのかな、フロントは。TSならVercelで、裏がPythonならCloud Runかな。

GW頃になるかな、検証するの。楽しみ。

React x TypeScript、フィーリングッド

oukayuka.booth.pm

こちらのReact3部作を全部買ったんですが、読み終わったらメインにも書くけど、このシリーズは最高です。

近年はPythonをメインにすることが多く、TypeHintも真面目に使ってこなかった。Flutterやり始めてDartを使うようになり、Dartの癖のなさに助けられた。

Flutterにすんなり入れたのはDartのおかげだけど、裏返すとReact/Vueからは距離をとっていたのよね。モダンJavaScriptで知ってるのってアロー演算子ぐらいだし。どこかで再入門したいと思っていた。で、上記の本を買った。

おかげさまで、JavaScriptの言語仕様・関数型プログラミング、TypeScriptによる型安全など、モダンJavaScriptの基本を知ることができた。実際コードを書いてみると、すごく気持ちよく書けた。特に関数型。

型を安全に引き回すのは気持ちがいいし、関数をチェーンする書き方は慣れると宣言的で簡潔になる。スッキリする。単純に左から右へ評価されて最終的な値へ到達する宣言(式)を書くスタイルは、副作用も起きにくい。StatementとExpressionの違いを肌で感じられたのは、この本のおかげ。

3年ぐらい前にVueをやった時、正直あんまり楽しくなかったのよね。リアクティブなテンプレートエンジンを書かされている感で「ほほ〜こいつは新しいな〜」という刺激は薄かった。v-if っとか v-for とか覚えることが多いし。Flutterを覚えると、 item.map( (e) => Text(e)) 的な感じで、モデルのデータからWidgetをmapで引き回させてくれよって思う。そのニーズに応えてくれるのが、もちろんReactだ。

Reactは、Flutterのように(Flutterの始祖はReactだから当然だけど)ビューのレンダリングから状態管理までをコードで完結できるスタイルなので、これだよな〜と。ReactはモダンなJavaScriptが書けないと真価が出ない。全てがビューをレンダリングする関数の組み合わせで実装するという「関数型ストロングスタイル」に触れてみたら、非常に刺激的で楽しいものでした。これに慣れるとクラスベースのオブジェクト指向は無用の長物感がある。

というわけで、Diverseさんと同様に、今後はTypeScriptとDartの2つをメインにやっていきです。

Pythonを始めとして多言語でも関数型のアプローチは出来ると思うけど、FEを「React/Flutter」って決めたので、バックエンドもTypeScriptでよくねってなっちゃったのよねー

HerokuのJawsDBでmax_questions error

Herokuで借りているMySQL(JawsDB)で、2万件弱のSQLを発行する処理(データの一括登録)を投げてしまったら、以下のエラーが出た。

"User 'hogehogehoge' has exceeded the 'max_questions' resource (current value: 18000)")

devcenter.heroku.com

sharedプランの安いインスタンスなので、 1時間あたりに実行可能なクエリの数を超えてしまった模様。困ったことにこのエラーを踏んでしまうと、1時間経過しないとリセットされず、一切のSQLが実行できなくなってしまう。実質ダウンだよね。ドキュメントに明言されている。

The ‘max_questions’ value is a limit that is placed on shared plan accounts to help preserve computing power for other shared plan users on the same server. It is essentially a limit on the number of queries that can be executed from that account in an hour. After 1 hour of reaching the limit, the limit will be reset and queries can again be executed.

After 1 hour of reaching the limit, the limit will be reset and queries can again be executed.

なるほどね。

大量のSQLを投げる必要がある場合、1つのクエリにまとめないとだめだな。SQLAlchemyでadd_allしたとしても、SQL文がリストの数だけ作られるっぽい。MySQLクライアントとかで流し込んだほうが確実だが、それでいいのか俺。

2022年はフロントエンドの年になる(個人的に)

あけおめあけおめ。

2021年からFlutterマンになった。フロントエンドは、技術以外の課題にも向き合えるのが自分に向いている。サービスフローであったり、UXのあり方であったり。

Flutterは中級駆け出しにはなれたので、次はFlutterのマザーにあたるReactをやりつつ、モダンなWebブラウザベースのWebアプリケーションの作り方を一新しようと思う。Pythonは一旦忘れて、TypeScriptだけで書ききってみたい。そういうフルスタックフレームワークも出てきたし、メンテナー日本人だし。

Webアプリケーションを学び直す場合、フルスタックフレームワークに入門するのが効率が良いと思っている。開発全体の流れが網羅的につかめるのと、今時な組み合わせがわかる。API通信はきっとGraphQLだろうし、クライアントはNext.js、ORMもある。5年周期でだいたいかわるよね、こういうの。

バックエンドはPaaSが豊富にある。すごい便利。VPS立てるのアホみたいだもん。Render/Firebaseの組み合わせで生きていける気がする。AWSは使いこなせればすごく良いと思うけど、秒で数十しか来ないWebアプリのデプロイするだけなら、使い込む余地が少ない。ファイル関係は全部S3にアップロードすればいいだけだし。

  • SSH開けなくていい
  • コンテナがデプロイできる
  • オートスケールする
  • モニタリング/DBもついてくる
  • バッチ用にCronがある

TypeScriptとNext.js、覚えるぞ〜。

FlutterのWidgetTest攻略メモ(随時更新)

WidgetTestを書いていて、Riverpodでビューの操作を行うクラスを全部DIして、ProviderのOverrideでMockに差し替えることをやっている。Mockにはmocktailを使っていて、このテスト戦略はシンプルでよい。サクサクテストが書ける。

riverpod.dev

どうせ忘れるので、WidgetTestを書いてきて遭遇したエラー内容をメモる。RiverPodのバージョンはもちろん1.0だ!

FutureメソッドのMock

Flutter書いているとtype 'Null' is not a subtype of type 'Future<T>のエラーにであう。Futureを使っているロジックは、明示的にちゃんとMockで値を返すように設定しないとアカン。

今回はboolだったのでこうなった。any()はどんな引数でもええでというやつ。returnしないとnullが返るから気をつけよう。

どうせ違うのは戻りがあるかないかぐらいで、ProviderでDIするロジックってイベントハンドラなので基本voidだから、もうちょい簡単になるかも。

    // 戻り値あり、引数あり
    when(() => fakeService.doSomething(any())).thenAnswer((_) async { return Future<bool>.value(true);});
    // 戻り値なし、引数あり
    when(() => fakeService.doSomething(any())).thenAnswer((_) => Future.value());
    // 引数も戻り値もなし
    when(fakeService.doSomething).thenAnswer((_) => Future.value();});

ViewModelとして機能するStateNotifierにHTTPでAPI取ってくる処理までも内包していいのか、ちょっと悩んでる。今は外してる。つまり、Repositoryの役目を果たすクラスはRiiverPodのProvider、画面の入出力に必要な値をひとつのPageStateにまとめて、StateNotifierで管理してる。ま、これで動くからそれでいいかなぁ。

FutureProviderの上書き

FutureProviderで保有している型(ここではHogePageState)に対し、てきとーな値を入れておく。ローカルストレージ系のものは、ローカルストレージからXXを取得してオブジェクトに入れた前提で動かせば良いと思われる。

SharedPreferencesをProviderでキャッシュする作戦をとっている場合、SharedPreferencesをMockしたProviderを作るのがちょっと面倒かもしれん。getの引数でthenAnswer連打の印象。書いてないから違ったらごめんね。

      hogeFutureProvder.overrideWithValue(const AsyncValue.data(HogePageState(
         name: "hoge", type: "foo")))

No Media Query

MaterialAppで囲ってない。

Could not find a generator for route RouteSettings

Mockで適当に差し込んだMaterialAppにRoutesの定義がないので、画面遷移に失敗する。

Widgetの型を指定し、そこに含まれるテキストで検索する

widgetWithTextが俺たちにはあった。

    await tester.tap(find.text("Press"));
    await tester.pumpAndSettle();
    expect(find.widgetWithText(AlertDialog, "Dialog Pressed"), findsOneWidget);

findのAPI、全部見たほうがええな。

StateNotifierProviderのMockができない

こういうコードを書くとProviderExceptionが発生し、StateNotifierProvider#addListenertype 'Null' is not a subtype of type '() => void'というエラーが出る。voidの関数が入るべき所にNull入れるなよボケってところ。

class FakeFooStateNotifier extends Mock implements FooStateNotifier {}

  testWidgets('ItemPage Search Test', (WidgetTester tester) async {
    final fakeViewModel = new FakeFooStateNotifier();
    await tester.pumpWidget(ProviderScope(
        overrides: [
          fooState.overrideWithValue(fakeViewModel),
        ],
        child: MaterialApp(
          home: FooPage(),
        )));
  });

addListenerがnullだったら適当にvoid Function作ったろかでノリでwhen(() => fakeViewModel.addListener(any())).thenReturn(() => {});とか書くと、stateが与えられていないせいかBad state: Tried to read the state of an uninitialized providerという別の例外がでた。

1.0以前はfakeViewModel.state.overrideWithValue(mock)みたいな書き方ができたけど、1.0の場合はstateがprotectedなので、外からアクセスできない。

StateNotifier自体がMockできないので、stateを操作する以外のロジック(例えばローカルストレージ読み込み)を書いてはいけないと思われる。Providerのrefを使ってStateNotifierProviderを読み込ませ、そこで状態を変更しよう。StateNotifierとStateNotifierを操作するサービスクラスの2つに分けたほうがいいな。Mockで差し替わらないというのが最大の理由だけど。

同じ問題に直面した人がIssueを立てているけど、多分スルーされる気がする。

github.com

うっかりSharedPreferenceを読み込んだコードをMockしないで死亡

この場合、エラーも何も出ないのよね。Widgetテストの宿命かもしれないけど、テスト対象のウィジェットで例外吐いている内容を捕まえられない。デバッグモードなら行けるのかなぁ...

SharedPreferenceを参照してるコードをMockで差し替えるのを漏れていて、画面遷移に失敗しており、画面遷移するはずのテストにコケていた。慣れの問題だろうけど...

Widgetテスト書いてると、きちんと外部ソースとビューのレンダリングの責務が分けられるのは実感する。その意味でクリーンというか疎結合になる。

あと、Golden Test(事前のスクショとテスト操作後のスクショ比べるやつ)は、あんまり有効性を感じない。ビューのリグレッションを検知することより、インテグレーションテストで、外部ソースと適切に連携できているかテストするほうが、品質は高まる気がしているため。