Closed60

2023.02 日々の学び

ひろとひろと

Command Query Separation

メソッドを2種類に分割するデザインパターン

  • Query:状態変化を伴わない. 冪等
  • Command:状態変化する. modifierやmutatorと呼称すべき

Command Query Responsibility Segregation

モデルを分離するデザインパターン

  • Queryモデル:only read from database
  • Commandモデル:update database

参考文献

ひろとひろと

Json Web Token

What

HTTPヘッダーやクエリパラメータなどスペースに制約がある環境で使うことを前提に、JSON形式のデータをURLセーフでコンパクトな形式にしたもの

  • URLセーフする
    • JSONデータをBASE64URLエンコードする
  • コンパクトにする
    • よく使われるデータ項目の名称を省略形にすることでJSONのキー名を短くする。またこれによりJSONデータをコンパクトにする

Why

HTTPヘッダーやクエリパラメータにJSONデータをうまく載せられるようにするため

Json Web Signature

What

Why

JWTの改竄に気づけるようにするため(防ぐわけではない)
データの盗聴・改竄は可能である点に注意

改竄に気付けるので、リソースサーバーから認可サーバーへの問い合わせが不要になる

資料

ひろとひろと

Refresh Token

Access Tokenの有効期限が短いため、頻繁に期限切れとなってしまい、アクセストークンの取得フローも頻繁に実行する必要があります。そこで、Refresh Tokenを利用して煩わしい部分を回避するという理由のために存在します。

資料

ひろとひろと

Platform依存なパッケージを使ったFlutterのUnit Test

「ストレージに書き込む」処理がUnit Test内にあると

PlatformException(channel-error, Unable to establish connection on channel., null, null)

channelにつながらないよってエラーになる。

マルチプラットフォームなpluginの仕組み

https://docs.flutter.dev/development/platform-integration/platform-channels

Flutter pluginのテスト

https://docs.flutter.dev/development/packages-and-plugins/plugins-in-tests

  • Flutter pluginは2つのパートを持つ
      1. APIとなるdartコード
      1. プラットフォーム依存(kotlin, swift)なコード

2番目のプラットフォーム依存コードはflutterビルドで行われるため、unit testやwidget testでは利用できない.

これを避けるための方法は4つある

  • プラグインをWrapした独自クラスをモックする
  • プラグインのAPIをモックする
  • プラグインのPlatform Interfaceをモックする
  • Platform Channelをモックする
    • デメリット
      • プラグインの内部実装をモックすることになるので、プラグインのアップデートの影響を受けやすい
      • Platformごとにモックする必要があり、テストを実行する環境に依存する
      • Platform Channelは型が辞書の場合があり、正しくモックするのがめんどくさい

path_providerのunit test

https://pub.dev/packages/path_provider#testing

資料

ひろとひろと

Four Keys

  • 速度
    • デプロイの頻度 - 組織による正常な本番環境へのリリースの頻度
    • 変更のリードタイム - commit から本番環境稼働までの所要時間
  • 安定性
    • 変更障害率 - デプロイが原因で本番環境で障害が発生する割合(%)
    • サービス復元時間 - 組織が本番環境での障害から回復するのにかかる時間

資料

ひろとひろと

Github Actionsのトリガー push or pull_request

  • pushイベントではpathsが利用できない
  • pushがトリガの場合、常にHEADとそのひとつ前の差分を見る
  • pull_requestはマージ元との差分を見る
on:
  pull_request:
    types:
      - opened
      - reopened
      - synchronize
    paths:
      - frontend/lib/**
      - frontend/test/**
      - frontend/pubspec.yaml
      - frontend/api/client/**
    branches-ignore:
      - renovate/**
  workflow_dispatch:

資料

ひろとひろと

Flutter CI(キャッシュなし)

name: Frontend Flutter CI

defaults:
  run:
    working-directory: frontend

on:
  pull_request:
    types:
      - opened
      - reopened
      - synchronize
    paths:
      - frontend/lib/**
      - frontend/test/**
      - frontend/pubspec.yaml
      - frontend/api/client/**
    branches-ignore:
      - renovate/**
  workflow_dispatch:

jobs:
  flutter-ci:
    runs-on: ubuntu-latest
    # runs-on: macos-latest
    timeout-minutes: 10

    steps:
      - uses: actions/checkout@v3

      # - name: Set Flutter Version to Environment Variable
      #   run: echo "flutter_version=$(cat .fvm/fvm_config.json | jq -r .flutterSdkVersion)" >> $GITHUB_ENV

      # - name: Search Flutter Cache
      #   uses: actions/cache@v2
      #   with:
      #     path: opt/hostedtoolcache/flutter
      #     key: ${{ runner.os }}-flutter-install-cache-${{ env.flutter_version }}

      - name: Install dart
        uses: dart-lang/setup-dart@v1.4

      - name: Install fvm
        run: dart --version
      
      - name: Check Dart Version
        run: dart pub global activate fvm

      - name: Setup flutter
        run: fvm install

      - name: Check Flutter version
        run: fvm flutter --version

      - name: Install dependencies
        run: fvm flutter pub get

      - name: Add dotenv
        run: |
          touch .env
          touch .env.dev

      # - name: Analyze
      #   run: |
      #     fvm flutter analyze --no-fatal-infos
      #     fvm flutter pub run dart_code_metrics:metrics analyze lib --no-fatal-warnings

      - name: Test
        run: fvm flutter test  --coverage --coverage-path=coverage/lcov.info

      - name: Show Coverage Report
        uses: zgosalvez/github-actions-report-lcov@v1
        with:
          coverage-files: coverage/lcov.info
          minimum-coverage: 0
          artifact-name: code-coverage-report
          github-token: ${{ secrets.GITHUB_TOKEN }}
ひろとひろと

mockito / NamedArgumentを固定してないとNo stub was foundになる

問題

whenでstubしてるにも関わらず

FakeUsedError: 'myFunction'
No stub was found which matches the argument of this method call:
        final mockError = Exception();
        when(
          mockApiClient.myFunction(),
        ).thenThrow(mockError);

引数も特にいらないはずと思い込んでいた。

Future<ClientResponse> myFunction({int? paramA=1}) async {...}

解決策

        final mockError = Exception();
        when(
          mockApiClient.myFunction(
            paramA: anyNamed('paramA'),
          ),
        ).thenThrow(mockError);

資料

ひろとひろと

モノレポ構成の時に、特定のworkflowでBranch Protectする

CIの成否でブランチプロテクトしたいが、モノレポ構成なのでフロントエンドのCIによるプロテクトがバックエンドにも影響する

Branch Protection Rules を通すには、成功したように見せかける必要があります。そのため、job 単位では実行する必要があり、step 単位ではスキップしていくという流れになります。

なるほど。。。

資料

ひろとひろと

Flutter / ログアウト・アカウント削除をした時はpushNamedAndRemoveUntilで履歴を消す

RoundButton(
              onPressed: () {
                  Navigator.of(context).pushNamedAndRemoveUntil(
                    Routers.startPage,
                    ModalRoute.withName(Routers.root),
                  );
              },

ページが消えないとstateが初期化されない。
なので1回目のアカウント削除は成功するが、2回目は失敗することがある。

資料

ひろとひろと

DartのLinter

  • Why
    • エラーを避けるプラクティスパターンのコードを担保する
  • What
    • コードに問題点がないかを確認する静的解析ツール
fvm flutter analyze --no-fatal-infos
fvm flutter pub run dart_code_metrics:metrics analyze lib --no-fatal-warnings

DartのFormatter

  • Why
    • 統一された書き方の読みやすいコードを担保する
  • What
    • コードのスタイルをチェックするツール
fvm flutter pub run import_sorter:main {staged_files}
fvm flutter format --fix {staged_files}

資料

ひろとひろと

Lefthookで、HEADとremoteとの差分があればトリガーしたい

git diffで頑張る方法 → 失敗

set upstreamしてない1回目のプッシュでは失敗する

git diff --name-only HEAD @{push}
fatal: ambiguous argument '@{push}': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

post-checkoutした時にリモートブランチを強制的に作る → 微妙

post-checkout:
    commands:
      set-upstream-branch:
        run: git push

pre-pushトリガーは、変更ファイルがないので起動しない。
副作用がありそうで怖い方法だな・・・

defaultブランチとの差分でトリガーする

余分な実行はありそうだけど仕方ない

git diff --name-only HEAD develop

資料

ひろとひろと

Lefthook Config

https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md

Git Hook

https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md#git-hook
https://github.com/evilmartians/lefthook/blob/master/internal/config/available_hooks.go

filesは2ヶ所にあるので注意

https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md#files-global
https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md#files

runで使える変数

https://github.com/evilmartians/lefthook/blob/bfed65c69dfefa2510ec4d503b60f6b28a59f40d/internal/config/files.go
https://github.com/evilmartians/lefthook/blob/d7b107cdb4d242386867cb0baf7be28afef6287a/internal/git/repository.go#L20

  • files
    • 任意のコマンド結果を保持する
  • push_files
  • all_files
  • staged_files
const (
	cmdRootPath    = "git rev-parse --show-toplevel"
	cmdHooksPath   = "git rev-parse --git-path hooks"
	cmdInfoPath    = "git rev-parse --git-path info"
	cmdGitPath     = "git rev-parse --git-dir"
	cmdStagedFiles = "git diff --name-only --cached"
	cmdAllFiles    = "git ls-files --cached"
	cmdPushFiles   = "git diff --name-only HEAD @{push} || git diff --name-only HEAD master"
	infoDirMode    = 0o775
)
ひろとひろと

Lefthookのexclude

正規表現でファイルやディレクトリを指定すると、{files}から対象が削除される
対象から消えているかどうかは lefthook run -vで確認できる

lefthook -v run pre-commit
Lefthook v1.2.1
RUNNING HOOK: pre-commit
Files before filters:
[frontend/api/client/sample.dart frontend/lib/sample.dart frontend/lib/sample.freezed.dart frontend/lib/sample.mocks.dart]
Files after filters:
[./lib/sample.dart]
Files after escaping:
[./lib/sample.dart]
Executing command is: fvm flutter pub get && fvm flutter pub run import_sorter:main ./lib/sample.dart && fvm flutter format --fix ./lib/sample.dart && git add ./lib/sample.dart
pre-commit:
  parallel: true
  commands:
    flutter-format:
      root: "frontend/"
      files: git diff --cached --name-only HEAD -- '*.dart'
      glob: "*.dart"
      exclude: '(?:\.(?:freezed|g|mocks|gen)\.dart|frontend\/api)'
      run: >
        fvm flutter pub get &&
        fvm flutter pub run import_sorter:main {files} &&
        fvm flutter format --fix {files} &&
        git add {files}

正規表現
http://rtilabs.rti-giken.jp/files/2011_11_02/index.php
https://chat.openai.com/chat

資料

ひろとひろと

dioをmockして自動生成したOAS Clientからのmockとする

OAS Clientそのものをmockすると、mock OAS Clientのrequestとresponseを作る必要がある
自動生成されたクラスを組み合わせてrequestとresponseを作るのは面倒なので、APIからのjsonレスポンスを作る方が楽かと思い実験。

// Package imports:
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openapi/openapi.dart';

final dioProvider = Provider<Dio>((ref) {
  final dio = Dio(
    BaseOptions(
      baseUrl: '/api/v1',
      headers: <String, dynamic>{'Accept': 'application/json'},
    ),
  );
  return dio;
});

final openapiClientProvider = Provider((ref) {
  final dio = ref.read(dioProvider);
  return Openapi(dio: dio);
});
  group(
    'Repository Test',
    () {
      late ProviderContainer container;
      late DioAdapter dioAdapter;

      final dummyEndpoint = '/books';

      setUp(
        () async {
          final dio = Dio(
            BaseOptions(
              validateStatus: (status) => true,
            ),
          );
          dioAdapter = DioAdapter(dio: dio);

          container = ProviderContainer(
            overrides: [
              dioProvider.overrideWithValue(dio),
              openapiClientProvider,
              apiProvider,
              repositoryProvider,
            ],
          );
        },
      );

      group(
        'fetchBooks',
        () {
          test(
            '[正常系]',
            () async {
              // Dio Client Mock
              dioAdapter.onGet(
                dummyEndpoint,
                (server) {
                  return server.reply(
                    200,
                    <String, dynamic>{
                      'list': [
                        {
                          'id': 123,
                          'title': 'ティアムーン帝国物語',
                        },
                      ]
                    },
                  );
                },
              );

              final repository = container.read(repositoryProvider);
              final response = await repository.fetchBooks();
              response.when(
                success: (value) => expect(value.length, 1),
                failure: (error) =>
                    fail('Processing is not expected to be failure'),
              );
            },
          );
        },
      );
    },
  );

資料

https://zenn.dev/shimizu_saffle/articles/http-mock-adapter
https://pub.dev/packages/http_mock_adapter/example

ひろとひろと

Flutter Integration Test

Finder

Widgetを探す方法
https://qiita.com/KKusumi/items/f458a42bbbf1958ad8d1

WidgetsFlutterBinding / IntegrationTestWidgetsFlutterBinding

https://qiita.com/kurun_pan/items/04f34a47cc8cee0fe542

void main() async {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  WidgetsFlutterBinding.ensureInitialized();

WidgetsBindingとは

The glue between the widgets layer and the Flutter engine.

runApp()を呼び出す前にFlutter Engineの機能を利用したい場合にコールするらしい

資料

https://docs.flutter.dev/testing/integration-tests

ひろとひろと

Flutter / Integration TestするときFirebaseをどう初期化する?

void main() async {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp();

initializeしているはずなのだけど・・

 [core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()

こうかも

void main() async {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  // WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: dev_firebase.DefaultFirebaseOptions.currentPlatform,
  );

資料

https://firebase.flutter.dev/docs/overview/#initialization

ひろとひろと

NeRF

Representing Scenes as Neural Radiance Fields for View Synthesis

モデル

Radiance Fields = NeuralNet(空間の座標, 視線の角度)
輝度(色と密度) = NeuralNet(空間の座標, 視線の角度)

学習


これはカメラ

カメラからある物体を見たときに、光の通る道の「輝度」をモデルが出力する
道の上の輝度を積分すると、カメラから見た画像の1ピクセルの色と一致する
なので、カメラ方向から輝度を積分して得た画像と、実際の画像との差分が小さくなるように学習する

NeRF研究の分類


資料

https://blog.albert2005.co.jp/2020/05/08/nerf/
https://arxiv.org/pdf/2210.00379.pdf

ひろとひろと

DNS

ドメイン名とIPアドレスとの対応づけを行う仕組み



https://qiita.com/hypermkt/items/610b5042d290348a9dfa
https://speakerdeck.com/excitejp/2022nian-du-xin-zu-ji-shu-yan-xiu-dns-jiang-yi?slide=10
https://jprs.jp/glossary/index.php?ID=0145

TLD: Top Level Domain

example.co.jpjp

FQDN: Fully Qualified Domain Name

www.amazon.comのようにホスト名 www + ドメイン名 amazon.comのこと

https://www.kagoya.jp/howto/it-glossary/domain/fqdn/

DNSサーバーの種類: キャッシュDNSサーバー + 権威DNSサーバー

  • キャッシュDNSサーバー
    • クライアントからのリクエストを受け付ける
  • 権威DNSサーバー

ICANN

https://ja.wikipedia.org/wiki/ICANN
ドメインネームの管理団体

DNSルートサーバーは世界で13ヶ所

https://root-servers.org/

*.root-servers.netというホスト名を持つ
*にはaからmまで入る
日本のDNSルートサーバーは m.root-servers.net

ひろとひろと

dig

Domain Infomation Groper = dig

8.8.8.8で動作しているDNSサーバーに google.comの名前解決をリクエストする

dig google.com @8.8.8.8

8.8.8.8はGoogleが公開しているDNSキャッシュサーバー
https://ja.wikipedia.org/wiki/Google_Public_DNS

逆引き:IPアドレスからドメインを検索する

dig -x 8.8.8.8

trace

dig @8.8.8.8 +trace stackoverflow.com

資料

https://zenn.dev/tetsuya/articles/fc6621a352418f44d2ad
https://zenn.dev/koyamaso/books/cbc1f9f136634c/viewer/ee11a9
https://dnsops.jp/event/20150724/LT-simamura-1.pdf

ひろとひろと

gTLD:Generic Top Level Domain

国や地域によらないトップレベルドメイン(TLD)
com, info, net, org, biz, name, pro

ひろとひろと

glue records

https://jprs.jp/glossary/index.php?ID=0185

権威DNSサーバーがcomを管理していて、一部のドメイン google.comを別の権威DNSサーバーに委任している時
「委任先のネームサーバーに聞いてくれ」とクライアントに返答するときに付加する情報のこと。
これがないと、クライアントがたらい回しになる。


ひろとひろと

secretファイルをgithub管理する

https://www.tweeeety.blog/entry/2021/05/08/034624
https://www.tweeeety.blog/entry/2021/05/10/090000

目的

  • secretファイルがなくて動かない
  • secretファイルを持ってる人からDMで送ってもらう
  • そもそもsecretファイルが必要なことに気づかない

といった面倒を回避したい。
一方でsecretファイルが流出することのないようにしたい

方法

を使う

手順

brew install gnupg
gpg --gen-key
brew install git-secret

ひろとひろと

Finderでドットファイル表示

defaults write com.apple.finder AppleShowAllFiles TRUE
killall Finder
このスクラップは2023/03/01にクローズされました