Vue Composition API の Composables を調べていたらドキュメントとか各種サイトで書かれているコードにモヤッとしたのでメモ
せっかくロジックをモジュールに切り出したのに、モジュールの外で state を書き換え可能な形で export しているコードをよく見かける
例えば以下のようなコードで
// sample.ts
import { ref } from 'vue'
export const useSample = () => {
const count = ref(0)
const increment = () => { count += 1 }
return {
count,
increment
}
}
<script lang="ts" setup>
import { useSample } from './sample'
const { count, increment } = useSample()
count.value = 100 // (1)
</script>
<template>
<div>
<div>{{ count }}</div>
<button type="button" @click="increment">increment</button>
</div>
</template>
(1)
で書き換えができてしまう
変更用の関数を作成している意味がなくなってしまうし、これを検出するのが人力になってしまうのも辛い
computed
を使えば型のレベルで再代入が不可になるので
import { computed, ref } from 'vue'
export const useSample = () => {
const count = ref(0)
const increment = () => { count += 1 }
return {
count: computed(() => count.value),
increment
}
}
こんな感じで書いた方が安全になる
パフォーマンスについては何も計測してないけど、書き換えできないと保証されることの方が自分にとっては大事なので、こっちで書いた方が良いと思うんだけどなぁ…
これは Google Cloud Platform Advent Calendar 2021 4 日目の投稿です。
今日は Secret Manager についてです。
今年は無料枠に追加されるなどアップデートがありました。
ただその中にはベストプラクティスでは使うのは避けた方が良いとされているものがあります。
例えば Cloud Run および Cloud Functions では、 Secret を
という機能が提供されるようになりました。
ところがこれらは
ファイル システムでシークレットにアクセスできると、攻撃者がシークレット マテリアルを読み取ることができるため、ディレクトリ トラバーサル攻撃などのアプリケーションの脆弱性が深刻化する可能性があります。
環境変数を介してシークレットが使用されると、デバッグ エンドポイントの有効化やプロセス環境の詳細をログに記録する依存関係などの構成ミスにより、シークレットが漏洩する可能性があります。
という理由で可能な限り避けるように書かれています。
ただ現実として環境変数で設定できると開発者ごとの違いを吸収しやすいのは事実です。
そこで Cloud Run には実行するコンテナに自動で設定される環境変数があるので、
package main
import (
"context"
"os"
secretManager "cloud.google.com/go/secretmanager/apiv1"
secretManagerPB "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
)
func FetchPassword() string {
// Cloud Run のみに存在する環境変数をチェックして
_, ok := os.LookupEnv("K_SERVICE")
if !ok {
// 未設定の場合には環境変数から取得
return os.Getenv("PASSWORD")
}
// 設定されている場合には Secret Manager にアクセス
ctx := context.Background()
c, _ := secretManager.Client(ctx)
r := &secretManagerPB.AccessSecretVersionRequest{ Name: "1" }
s, _ := c.AccessSecretVersion(ctx, r)
return s.Payload.Data
}
といった形で使っていくのが良さそうだと思っています。
ということで良い機能だなと思っても 1 回立ち止まってベストプラクティスを読んでみる方が良いというお話でした (自戒を込めて)。
Node から OS の C API を触りたくて Native Addon を生成していて、使う側はいつも通り TypeScript でやりたい
ディレクトリ構成はこんな感じ
.
|- src
| |- index.ts
| `- sample.node
|- package.json
|- tsconfig.json
`- yarn.lock
Addon は sample 関数だけ export しているとして、 index.ts はこんな感じで import する
import { sample } from './sample.node'
sample()
そうするとモジュール名の箇所で
error TS2307: Cannot find module './sample.node' or its corresponding type declarations.
となる
Vue.js の SFC を TypeScript で使う時と同じエラーの出方だなーと思ったので、
declare module "*.node" {
export function sample(): string;
}
という型定義ファイルを index.ts
の隣に置く
これでエラーが消えて、ちゃんと補完が効いたり解析が正しく動くようになる
ただこれだと複数の addon がある時には同じ型定義になってしまう
これは Wildcard module declarations という JS 以外のファイルを扱うケース向けの機能を使っている
今回の場合は suffix で判定しているため、以下のようにすると複数の addon に違う型が適用される
// import {} from './sample.node' の時に使われる
declare module "*sample.node" {
export function sample(): string;
}
// import {} from './foobar.node' の時に使われる
declare module "*foobar.node" {
export function foobar(): string;
}
解決方法として妥当かはよくわからないけど、まあなんとかなる
Monorepo なら多少楽になるかもしれないけど、どちらにせよ自分で書くのは面倒なので C++ の実装から型定義を自動生成したいなー
Node Native Addon を作ってみようと思ったので、まずは node-addon-examples の first project を node-gyp から cmake-js に置き換えてやってみました
普段から使い慣れているので IDE は CLion を使って
ただ NODE_API_MODULE()
で C++ requires a type specifier for all declarations
だったり、いくつかエラーが出てしまってどうにもおかしい
C++ をろくに書いたことがなくてどこが悪いのかさっぱり思いつかないので、まずは以下のコマンドを実行してコンパイルしてみた
$ ./node_modules/.bin/cmake-js compile
これは成功してちゃんと動くものができてたので実装は問題ないはず
じゃあ何が違うんだろうということで、綺麗なところからビルドしてディレクトリを比較してみる
まずは元の綺麗な状態がこう
.
|- lib
| `- binding.js
|- node_modules
|- package.json
|- src
| `- hello_world.cc
|- test
| `- test_binding.js
|- CMakeLists.txt
`- yarn.lock
cmake-js compile
実行後にはこんな感じ
.
|- build
| |- Release
| | `- out.node
| `- (省略)
|- lib
| `- binding.js
|- node_modules
|- package.json
|- src
| `- hello_world.cc
|- test
| `- test_binding.js
|- CMakeLists.txt
`- yarn.lock
次に CLion で開いた時にはこうなる
.
|- cmake-build-debug
| |- Release
| | `- out.node
| `- (省略)
|- lib
| `- binding.js
|- node_modules
|- package.json
|- src
| `- hello_world.cc
|- test
| `- test_binding.js
|- CMakeLists.txt
`- yarn.lock
ということで生成されるフォルダが build か cmake-build-debug かというところが違った
これを調整したらエラーが消えるのかもということで、 CLion の設定で
Build, Execution, Deployment
> CMake
の順にたどって Profile
の Build directory
を build に設定してみる
設定を適用してロードを待ったらエラーが消えたのでめでたしめでたし
CLion は build directory に生成されたデータを元に解析対象を探してるっぽい
Windows での開発全くしてこなかったんですが、仕事のマシンで初めて Visual Studio 2019 をインストールしました
どんなのが入ってるのかとディレクトリを彷徨ってみたら、 git.exe がいたので単体で動くのか遊んでみました
Git for Windows はインストールしてない環境です
とりあえず簡単に PATH を通して
set PATH="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\CommonExtensions\Microsoft\TeamFoundation\TeamExplorer\Git\cmd";%PATH%
git version
してみたら正常に出力されました
git version 2.33.0.windows.1
細かいことは見てないけど、 Electron のソース取得 は無事に動いたので普通に使えそう
Build Tools for Visual Studio とやらにも入ってたら良いなと思いますが、調べるのは面倒くさい
お仕事で UTF-8 の CSV を Go で読む機会があったんですが、中を見たら BOM があるファイルでした
これを何も考えずに encoding/csv で読んでみたらエラーになるんですね
実際には要件のおかげで雑に対処したけど、 BOM の有無にかかわらず読むってどうしたら良いのかなと遊んでみました
ドキュメント には RFC 4180 に書いてある CSV をサポートしていると書いてあって、
このフォーマットは BOM について書いていないので知る必要がないと issue
に書いてありました
そりゃそうだってことで Seek の位置を調整してから Reader に渡すようにしてみたらちゃんと読めました
愚直にしか書けないからか頭の中がそのまま記述されるみたいで Go は書いてて気持ち良い
package main
import (
"encoding/csv"
"log"
"os"
)
func main() {
f, err := os.Open("sample.csv")
if err != nil {
log.Fatalln("Open failed", err)
}
b := make([]byte, 3)
_, err = f.Read(b)
if err != nil {
log.Fatalln("Read failed", err)
}
if !(b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xEF) {
f.Seek(0, os.SEEK_SET)
}
r := csv.NewReader(f)
rs, err := r.ReadAll()
if err != nil {
log.Fatalln("CSV Reader read failed", err)
}
log.Printf("%v\n", rs)
}
初めての iOS アプリを SwiftUI で書いてみたら、 NavigationLink の遷移が複数回発生したのでメモ
対処はしてないので、読み進めても何も解決策は書いてません
環境は
- Xcode 12.4
- Simulator (iOS 14.4)
挙動の再現するコードはこんな感じ
よくある NavigationBar のボタンで画面遷移するやつ
Timer を使用しているところは、実際に作ったものだとネットワーク経由のデータ取得をしてる
import SwiftUI
class ViewModel: ObservableObject {
@Published var message = "aaa"
init(_ number: Int) {
Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { _ in
self.message = "bbb"
print("timer end \(number)")
}
}
}
struct MainView: View {
@ObservedObject var vm: ViewModel
var body: some View {
NavigationView {
Text(message)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: SubView()) {
Text("Show")
}
}
}
}
}
}
struct SubView: View {
@ObservedObject var vm: ViewModel
var body: some View {
Text("Sub view")
}
}
これで Timer.scheduledTimer の block が実行される前に SubView に遷移すると、 block が実行されるタイミングで再度遷移する
ページのスタックが MainView -> SubView -> SubView になってしまうので、 NavigationBar の戻るをタップしても SubView が出る
なぜこの動きになるのかは調べたいけど、 Jetpack Compose の State Hoisting みたいに
状態管理を基点の View かその上にまで持ち上げる設計に直す方が優先な気がする
ちなみに SubView から ObservedObject を消すと発生しない
あとはこんな感じで toolbar から外すと遷移は 1 回だけになる
struct MainView: View {
@ObservedObject var vm: ViewModel
var body: some View {
NavigationView {
- Text(message)
- .toolbar {
- ToolbarItem(placement: .navigationBarTrailing) {
- NavigationLink(destination: SubView()) {
- Text("Show")
- }
- }
- }
+ VStack {
+ Text(message)
+ NavigationLink(destination: SubView()) {
+ Text("Show")
+ }
+ }
}
}
}
AndroidでJSONを操作するところのUnit Testを書こうと思ったら、Robolectricが動かなくて困ったのでメモ
環境
- Android Studio 4.0.1
- Robolectric 4.4
- targetSdkVersion 30
Robolectric 4.3.1からはリリースノートにある通り、API Level 29が対象の時にはJava 9が必須になっている
そしてAndroid Studio 4.0に同梱されているのはJava 8系なので動かない
解決の方法としては以下の2通りが考えられる
- Java 9以降のJDKをインストールする
- Robolectricの対象API Levelを下げる
個人的にはAndroid Studio以外にインストールするものがあるのは面倒だし、環境を作り直すときに確実に忘れるので 対象API Levelを下げる
方向で対処する
Robolectricで対象のAPI Levelを設定するには以下のような方法がある
- @Config annotationで指定する
- robolectric.propertiesで指定する
- RobolectricTestRunnerをextendする
ただどれも面倒だと思ってしまったので、今回は build.gradle.kts
を編集する
公式サイトにあるようにRobolectricは targetSdkVersion
に指定されているバージョンを対象にしている
このため、例えばbuild.gradle.ktsが以下のようになっているとAPI Level 29が使用される
android {
defaultConfig {
targetSdkVersion(29)
}
}
であるならば、testの実行時だけtargetSdkVersionを書き換えれば、他のAPI Levelを使用するよう設定できる
どうするかというと
android {
defaultConfig {
targetSdkVersion(29)
}
testOptions {
defaultConfig {
targetSdkVersion(28)
}
}
}
これでUnit TestとInstrumented Testの両方に作用する
Unit Testだけに絞るなら以下のようにネストすれば良い
android {
defaultConfig {
targetSdkVersion(29)
}
testOptions {
unitTests {
defaultConfig {
targetSdkVersion(28)
}
}
}
}
macOS CatalinaにNode.jsをインストールするとき、趣味でHomebrewを使わず、tar.gzをダウンロードしてインストールしている
tar.gzのものはApple Notary Serviceを通していないので、Gatekeeperの検証に引っかかって node
コマンドのプロセスに SIGKILL
が送信されてしまう
検証の通し方は簡単なんだけど、いつも忘れてしまうのでメモ
Finderでnodeの実行ファイルがあるディレクトリを開いて
- 実行ファイルを右クリック
- メニューの
開く
を選択
- ダイアログが出てくるので
開く
をクリック
仕事で使うのがVue.jsばかりだったのでReactとどう違うのか見てみる
とりあえずマウントした時の動作を
環境はVue.jsが 2.6.11
、Reactは 16.13.1
土台はこんな感じのHTMLで
<body>
<div id="app">
<span>Loading...</span>
</div>
</body>
まずはVue.jsから
ソースはこんな感じで
new Vue({
el: '#app',
render(h) {
return h('div', 'Hello, World!');
}
});
実行した結果はこうなってて、 div#app
が消えてる
<body>
<div>Hello, World!</div>
</body>
次にReactで、ソースはこう
ReactDOM.render(
React.createElement('div', null, 'Hello, World!'),
document.querySelector('#app')
);
結果は div#app > span
は消えるけど、 div#app
は残ってる
<body>
<div id="app"><div>Hello, World!</div></div>
</body>
VueとReactどちらもドキュメントを読んだら書いてあるので、ちゃんと読むのは大事