yn2011's blog

技術メモ

React Native (Expo) で開発した iOS / Android アプリの起動時間を計測する

前提

  • Expo SDK 45
  • react-native-startup-time v2.0.0

結論

react-native-startup-time を利用した。

import { getTimeSinceStartup } from 'react-native-startup-time';

// 省略...

export const Screen: React.FC = () => {
  const [launchTime, setLaunchTime] = React.useState(0);

  useEffect(() => {
    getTimeSinceStartup().then((time) => {
      setLaunchTime(time);
    });

  }, []);

  return (
        <Body>アプリ起動からコンポーネント表示までの時間:{launchTime / 1000}</Body>
  );
};

起動時間の定義(計算式)

react-native-startup-time の iOS / Android それぞれの実装を確認すると、以下の計算で起動時間を算出していることが分かった。

getTimeSinceStartup実行時の時刻 - アプリ起動直後の初期化における時刻

実装に興味のある方はこちらを参照

なぜ react-native-startup-time を使ったか

React Native で開発したアプリの起動時間を計測する方法は大きく分けると、①ライブラリを利用する方法と、②XCode / Android Studio を利用する方法がある。

今回は Expo の Managed Workflow を使用しており、②の方法で計測するには毎回 eject しなくてはいけなかったので①のライブラリを利用する方法を選択した。Expo の development Client を使用して開発している場合は、ライブラリで起動時間を計測できるようにしておけば JavaScript の変更の度に再ビルドする必要がないのも嬉しい。

逆にライブラリを使うデメリットとしてはプロファイルが取得できない点が挙げられる。これは②のツールを使用する必要があるので注意が必要。

ライブラリを使う場合、react-native-startup-time の他に react-native-perf-logger でも起動時間が計測できそうだったが、ライブラリを利用すると EAS Build で Android のビルドに失敗してしまったので選択できなかった。react-native-startup-time でやりたいことはできているので、まあこっちでいいかーという感じで最終的に react-native-startup-time を使うことにした。

エンジニアが業務用 mac の ディスク空き容量を 100GB 増やすためにやったこと

気づいたら業務用 mac のディスク容量の空きが 10GB ぐらいしかなくて困ったので色々やって 100GB 空けたという話。「mac ディスク容量」みたいにググってもゴミ箱を空にするとか全然参考にならないことしか出てこなかったのでこの記事を書いている。

業務用 mac でやってること

業務では Web のフロントエンドと、React Native を利用したアプリ(iOS / Android 両方)開発をやっている。

特に以下を使って開発している人には参考になるかもしれない。

現状を把握する

mac OS 標準のディスク管理ツール的なものでも大まかには把握できるが、キャッシュ系はすべて「その他」と「書類」に分類されるのであんまり約に立たない。 アプリケーションやダウンロード、ゴミ箱などを整理したのに全然容量が空かず、「その他」が100GB使用しているのは何??となりがち。更に噂によると「書類」と「その他」が重複カウントされている場合もあるらしく、これではまったく意味が分からない。

というわけでフリーのディスク分析ツールを使う。自分は、OmniDiskSweeper というやつを使ってみた。

www.omnigroup.com

正直この手のツールはマルウェアっぽいのも多い気がしていて、あんまりインストールしたくはなかった。root から地味に du して探索する手もあり、実際最初はやってみてたが、途中でしんどくて止めた。普通に visualize 系のツールを使ったほうが楽だと思う。

以下、OmniDiskSweeper でサイズが大きなディレクトリを見つけて手当たり次第に対応していった作業ログ。

Docker

Docker for Mac が Docker.raw という 30GB ぐらいの謎ファイルを持っていた。

以下のサイトで紹介されている通り、Docker for Mac の設定でディスク使用量を最小に変更する。

apple.stackexchange.com

それから $ docker system prune -a で不要なイメージ等を全部消して 1/3 以下ぐらいに減らせた。

XCode

無駄に仮想端末を作っていたので削除した。

$ xcrun simctl list devices で一覧から削除したい端末の UDID を特定して $ xcrun simctl delete 26B***

それとなんかよく分からないキャッシュがあったのでそれも全部 rm で消した。

~/Library/Developer/CoreSimulator/Caches/dyld/20G95
❯ ls
com.apple.CoreSimulator.SimRuntime.iOS-15-0.19A339
com.apple.CoreSimulator.SimRuntime.tvOS-15-0.19J344
com.apple.CoreSimulator.SimRuntime.watchOS-8-0.19R345

Android Studio

こちらも使ってない仮想端末と SDK を削除した。 仮想端末は普通に Android Studio から削除して、SDK はよく分からなかったので rm した。 ~/Library/Android/sdk/system-images 配下にあるはず。

yarn

.yarn-cache が巨大だったので yarn cache clean で全部消した。 yarn はそれなりに使うので別に消さなくても良かった可能性はある。

参考:https://stackoverflow.com/questions/39991508/how-to-clear-cache-in-yarn

npm

yarn と同様に $ npm cache clean --force

$ npm cache verify はキャッシュを最適化してくれるらしいが、あんまり変わらなかったので全消しした。

参考:https://stackoverflow.com/questions/55157862/npm-cache-clean-v-s-npm-cache-verify

Cypress

Cypress もなぜか巨大なキャッシュを Cypress のバージョンごとに持っていた。 $ cypress cache prune で削除できるらしいが、自分はディレクトリ毎 rm した気がする。

参考:https://docs.cypress.io/guides/guides/command-line#cypress-cache-command

gradle

.gradle/caches を rm

クローンしたリポジトリ

何気に1リポジトリで 1GB ぐらい消費しているものがあったりした。昔のプロジェクトで時々参照はするから削除はしたくないけどローカルで起動することは少ないなーみたいなやつは node_moduels だけでも削除しておくとディスク容量を大きく削減できる。

まとめ

大体以上の作業で 100GB 程度のディスク空き容量を確保できた。思ったよりキャッシュや不要な仮想端末等が多かった。そもそも業務用 mac のストレージ上限が 250GB なのは心許ないが、その制約がディスクの使い方を見直すきっかけになり、この記事を書くこともできたので良かったと思う(ポジティブ)

img 要素に width と height を指定してもレイアウトシフトする原因は属性値が auto だからかも

環境

tldr;

img 要素に width と height を指定するとレイアウトシフトしないと聞いて試してみたが、レイアウトシフトが改善しないケースがあった。img 要素の width と height に auto を指定していて画像を読み込むまで幅を決定できない場合にレイアウトシフトが発生する。

width と height でレイアウトシフトを防ぐ

以下の HTML をブラウザで描画するとレイアウトシフトが発生する

<img src="https://via.placeholder.com/1200x600" />
<p>text</p>

画像が後から読み込まれ、text が下に移動する。

以下の場合はレイアウトシフトしない。ブラウザがアスペクト比を理解するからである。この例では実際の画像の幅と属性値が一致するが、一致していない場合でも width と height の属性値で描画を決定できるのでレイアウトシフトは回避できる。

<img src="https://via.placeholder.com/1200x600" width="1200" height="600" />
<p>text</p>

詳細な説明は以下を参照

width:auto と height:auto を組み合わせるとレイアウトシフトする

属性値でアスペクト比を伝えている場合でも、以下のスタイルを当てるとレイアウトシフトが発生する。

.image {
  width: auto;
  height: auto;
}
<img src="https://via.placeholder.com/1200x600" width="1200" height="600" class="image" />
<p>text</p>

これは、width と height 属性を HTML で指定していたとしても、CSS で値を auto にすると画像が読み込まれるまで幅と高さを解決できないからだと考えられる。

例えば、以下のように width と height を 200 に指定したとしても、auto のスタイルを当てている場合には無視されて実際の画像の大きさ(1200*600) で描画される。

 <img src="https://via.placeholder.com/1200x600" width="200" height="200" class="image" />

なのでブラウザは画像が読み込まれた後にレイアウトシフトを発生させるしかない。

片方だけ auto の場合はレイアウトシフトしない

width と height で片方が auto の場合は、属性値とアスペクト比から片方を事前に計算可能なのでレイアウトシフトを回避できる。ただし、画像の実際のアスペクト比と、width と height から計算するアスペクト比が一致していることは前提である。

これはOK

.image {
  width: auto;
}

これもOK

.image {
  height: auto;
}

これも大丈夫

.image {
  width: 100%;
  height: auto;
}

img 要素に指定した width と height から計算されるアスペクト比と、実際の画像のアスペクト比が一致しない場合

img 要素に指定した width と height から計算されるアスペクト比と、実際の画像のアスペクト比が一致しない場合でも片方が auto であれば画像が持つ正しいアスペクト比で幅を算出してくれる。

例えば、これはアスペクト比が 1 / 1 だと認識されそうだが

 <img src="https://via.placeholder.com/1200x600" width="200" height="200" class="image" />

height を auto にしている場合は、画像を読み込んだ後に本来のアスペクト比を利用して height = width / 2 で 100 としてブラウザは表示してくれる。

しかし、本来の画像のアスペクト比は画像をブラウザが読み込むまでは解決できないはずであるため、読み込み後にレイアウトシフトは発生する。この場合のレイアウトシフトは先に height=200 で確保した縦幅が 最終的に 100 になるので下から上へと要素が移動することになる。

逆に、上記の例で width を auto にした場合は(少なくとも上下の)レイアウトシフトは発生しない。画像の本来のアスペクト比が何であれ、高さが 200 であるということは変わらないからだ。左右に要素が存在する場合には横幅が画像を読み込んだ後に決定されるのでレイアウトシフトするはずだ。

疑問

CSS の width と height は初期値が auto なのに、CSS で明示する場合としない場合で挙動が変わるのはなぜ?

参考

(Android) Expo Bare workflow に Firebase Crashlytics を導入する

Firebase Crashlytics を使うとアプリがクラッシュした際にエラーレポートを送信することができる。

iOS に導入する手順は(iOS) Expo Bare workflow に Firebase Crashlytics を導入する に以前書いた。今回は Android アプリに Firebase Crashlytics を導入する手順について書く。

環境

  • Expo SDK 44
  • custom development client 利用
  • Firebase コンソールからアプリを登録済み
  • Android Studio (Emulator) でアプリを起動

パッケージインストール

公式ドキュメントの手順通りにパッケージをインストールする。Firebase のサービス自体を初めて利用する場合は、@react-native-firebase/app のインストールが必要。

// 未インストールの場合
yarn add @react-native-firebase/app

yarn add @react-native-firebase/crashlytics

Android 向けのセットアップ

iOS では不要だったが、Android のビルド向けに設定が必要。手順は、Crashlytics - Android Setup の 1~4 を参照。

firebase.json を作成する

省略(iOS 導入の記事参照)

テストクラッシュを起こす実装をする

省略(iOS 導入の記事参照)

アプリをビルドし起動する

EAS Build 等を利用してアプリをビルドし Android Emulator で起動する。

正常に Crashlytics がレポート送信を行っていることを確認するため、Android Studio でログを表示する。ログの表示方法は、logcat を使用してログを書き込み、表示するを参照。

クラッシュを発生させる

自分の環境だと、crashlytics().crash() ではレポートを送信することができなかった。クラッシュを実行すると以下の画面が表示される。

f:id:pokuwagata:20220220150633p:plain

ログは以下になる。

2022-02-08 18:31:04.712 14816-15028/appId E/DevLauncher: DevLauncher tries to handle uncaught exception.
    java.lang.RuntimeException: Crash Test
        at io.invertase.firebase.crashlytics.ReactNativeFirebaseCrashlyticsModule$1.run(ReactNativeFirebaseCrashlyticsModule.java:83)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:226)
        at java.lang.Thread.run(Thread.java:923)

どうやら先にエラーをキャッチされてしまっているようである。

一方で、JS エラー(throw new Error()) を起こした場合は以下のログが表示されアプリはクラッシュした。

2022-02-08 18:52:44.727 16610-16947/appId D/RNFBCrashlyticsInit: isCrashlyticsCollectionEnabled final value: true
2022-02-08 18:52:44.738 16610-16947/appId E/Crashlytics: Crash logged. Terminating app.

アプリを再起動して少し待つと、Firebase コンソールからは以下のようなレポートを閲覧できる。(なお、JS エラーをレポート送信するかどうかは firebase.json で設定可能)

f:id:pokuwagata:20220220151138p:plain

まとめ

Expo Bare workflow を利用した Android アプリに Firebase Crashlytics を導入する手順を書いた。

development client を利用していると、ネイティブコードのエラーを正常にレポート送信することはできなかった。stand alone にビルドしていればクラッシュすると思うので正常にレポート送信は行われるのではないかと予想している。この点については今後検証したい。

(iOS) Expo Bare workflow に Firebase Crashlytics を導入する

Firebase Crashlytics を使うとアプリがクラッシュした際にエラーレポートを送信することができる。Expo Bare workflow のプロジェクトに Firebase Crashlytics を導入する手順について書く。

環境

  • Expo SDK 44
  • Firebase コンソールからアプリを登録済み
  • iOS シミュレータビルドで検証

パッケージインストール

公式ドキュメントの手順通りにパッケージをインストールする。Firebase のサービス自体を初めて利用する場合は、@react-native-firebase/app のインストールが必要。

// 未インストールの場合
yarn add @react-native-firebase/app

yarn add @react-native-firebase/crashlytics

cd ios/ && pod install

最終的に EAS build するとしても Podfile.lock の更新のため pod install は必要だと思う(多分)

firebase.json を作成する

プロジェクトディレクトリの直下に、firebase.json を作成する。今回は疎通確認のため以下の設定値を有効にする。

ネイティブコードではなく、React Native で実行した JavaScript のエラーもレポート送信の対象にしている。

{
  "react-native": {
    "crashlytics_debug_enabled": true,
    "crashlytics_auto_collection_enabled": true,
    "crashlytics_is_error_generation_on_js_crash_enabled": true,
    "crashlytics_javascript_exception_handler_chaining_enabled": true
  }
}

設定値のリファレンスは Firebase JSON Config を参照。

テストクラッシュを起こす実装をする

crashlytics().crash() を実行するとネイティブ領域でエラーを発生させアプリをクラッシュさせることができる。

<Button title="Test Crash" onPress={() => crashlytics().crash()} />

以下のような JS Error でも良い。

<Button title="Test Crash" onPress={() => throw new Error("test")} />

アプリをビルドしシミュレータで起動する

アプリをビルドし iOS シミュレータにインストールする。

デバッグ用に iOS シミュレータのログを確認できるようにしておく。

$ xcrun simctl spawn booted log stream --level debug --style compact | grep -i crash

crash で grep し、Crashlytics 関連のログのみが表示されるようにする。

以下のようにfirebase.json の設定値がログに出力されていればOK

2022-02-03 17:08:16.938 Df appName[63895:42a438] +[RNFBSharedUtils getConfigBooleanValue:key:defaultValue:] [Line 160] RNFBCrashlyticsInit crashlytics_auto_collection_enabled final value: 1

テストクラッシュ用に実装したボタンを押下するとアプリがクラッシュする。

再度アプリを起動すると以下のようなログが出力される。

2022-02-03 17:14:22.300 Df appName[64062:42b7c7] +[RNFBCrashlyticsInitProvider configureWithApp:] [Line 101] RNFBCrashlyticsInit initialization successful
2022-02-03 17:14:23.192 Df appName[64062:42b7d5] [com.apple.network:connection] [C6 4F05B069-6BF2-442B-9434-CF5F0223CBF1 crashlyticsreports-pa.googleapis.com:443 tcp, url hash: 668ea868, tls, definite, attribution: developer, context: com.apple.CFNetwork.NSURLSession.{4F3F6DE6-21E5-4E1C-8B27-6043E5450330}{(null)}{Y}{2} (private), proc: EA95ADB9-AD35-39EC-BB62-880027AC07B2] start
2022-02-03 17:14:23.192 I  appName[64062:42b7d5] [com.apple.network:connection] nw_endpoint_handler_start [C6 crashlyticsreports-pa.googleapis.com:443 initial path ((null))]

Crashlytics の初期化成功後に、ネットワーク通信が始まりレポート送信が行われる。アプリがクラッシュしたタイミングではなく、クラッシュ後にアプリを起動しないとレポートは送信されないことに注意。

Firebase コンソールでレポートを確認する

f:id:pokuwagata:20220212120955p:plain

送信したレポートを閲覧できていれば Firebase Crashlytics の導入に成功している。

crashlytics().setUserId(id) によってユーザの識別子を付与してレポートを送信することも可能。

まとめ

Expo Bare workflow を利用した iOS アプリに Firebase Crashlytics を導入する手順を書いた。

Bare workflow の Android の場合はパッケージインストール以外にも追加で build.gradle を編集する必要があるが、iOS の場合は比較的ラクに導入できると思う。

手元では Android でも既に導入に成功しているので別の記事で手順やハマりポイントを書きたい。

(Android) Expo bare workflow で 環境別に複数の applicationId を使い分ける

公式ドキュメントのExample: configuring development and production variants in a bare projectを参考にやってみたのでメモ。

環境

  • Expo SDK 44
  • bare workflow

applicationId とは

アプリの識別子。

すべての Android アプリには、「com.example.myapp」など、Java パッケージ名に似た一意のアプリケーション ID があります。 https://developer.android.com/studio/build/application-id?hl=ja

Expo manage workflow の app.json に存在する package プロパティはこの applicationId を設定している。また、iOS アプリにも同様の識別子は存在する。

概要

Android アプリのビルドバリアントという仕組みを利用する。環境別(dev, stg, prd...) にビルドバリアントを作成しそれぞれに applicationId を定義する。

ビルドバリアントは、ビルドタイプとプロダクトフレーバーの組合わせである。ビルドタイプが debug/release 、プロダクトフレーバーが dev/prd が存在する場合はビルドバリアントは 4つ存在することになる(dev-debug, prd-debug, ...)

したがって、厳密にはビルドバリアントに applicationId を定義するのではなく、ビルドタイプとプロダクトフレーバーそれぞれに定義している(どちらか片方でもいい)

今回の環境別に異なる applicationId を使い分けるという目的を達成するためには、このプロダクトフレーバーを使う。

build.gradle にプロダクトフレーバーを追加する

Expo の managed workflow から eject すると、/android/app/build.gradle が生成される。このファイルに新しいプロダクトフレーバーを追加する(今回は例として、development と production)

 buildTypes {...} // 省略
 // 以下を追記
 flavorDimensions "env"
    productFlavors {
        production {
            dimension "env"
            applicationId 'com.example.myapp'
        }
        development {
            dimension "env"
            applicationId 'com.example.myapp.dev'
        }
    }

ビルドバリアントを指定してビルドする

expo run:android の場合

expo run:android コマンドは --variant オプションを受け取ることができるので、ビルドバリアントを指定する。

ビルドバリアントは、ビルドフレーバーが development, ビルドタイプが debug の場合は以下のように指定ができる(ビルドタイプは大文字から始めることに注意)

expo run:android --variant developmentDebug

これで指定したビルドバリアントの applicationId を持つアプリをビルドできた。

EAS build の場合

ローカル環境で Android のビルドを行うと環境面で躓くことが多いので、自分は EAS build することの方が多い。

EAS build の場合は、eas.jsongradleCommand というプロパティを使ってビルドバリアントの指定を行う。

コマンドの書式は、(assemble|bundle)FlavorBuildType となっている。 assemble を指定すると、ビルドファイルの拡張子が.apk になり、bundle の場合は .aab になった。この辺の技術的な違いはよく分からない。 ビルドフレーバーとビルドタイプを大文字から始めることに注意(Upper Camel)

{
  "cli": {
    "version": ">= 0.35.0"
  },
  "build": {
    "development": {
     ...
      "android": {
        "gradleCommand": ":app:assembleDevelopmentDebug" // dev
      }
    },
    "production": {
      "android": {
        "gradleCommand": ":app:bundleProductionRelease" // prd
      }
    }
  },
  "submit": {
    "production": {}
  }
}

あとは、通常通りにプロファイルを指定して EAS build を開始すれば良い。

eas build --profile development --platform android

ビルドバリアント毎にアプリケーションの名前も変えたい

ホームスクリーン上に表示されるアプリケーションの名前もビルドバリアント毎に変えることができる。

プロダクトフレーバー毎に新しくディレクトリとファイルを作成する。development の場合だと以下になる。

android/app/src/development/res/value/strings.xml

このファイルに以下のように名前定義をする。

<resources>
    <string name="app_name">(dev)app-name</string>
</resources>

参考

2021 年を振り返る

技術

2021 年はプロダクトのグロースや新規構築を通じてずっと Next.js に触れ続けていた年だった。

現職で自分が関わるプロダクトは、事業領域の特性的にも機能要件があまり難しくないことが多い。複雑な状態を持つ SPA を開発しているというよりは、静的ページの自動生成と、静的ページを React で効率よく書くためのフレームワークとして Next.js の SSG を利用しているに過ぎない。それらは公式のドキュメントや過去の別プロダクトのコードベースなどの資産を活用すれば開発自体は難しくはない。一方で toC な感じのデザインをシュッと実装できることが必要で、自分はいわゆる Web 制作の経験がない。ちょっとこれは不味いなということで個人的に色々勉強して、HTML/CSS の知識体系を確立しようとしていた。

単なる知識ではなく、”知識体系”と書いたのは、特に CSS については、イディオムの丸暗記ではなく、なぜこういう動きになるのか? CSS 全体を支配する規則の中でどう説明可能なのか?みたいなことを意識していたからである。まあ分からんことも多いのだけど。

直近では技術調査として Expo (React Native) をよくやっている。もしかすると 2022 年は React Native をやる年になるのかもしれない。

引っ越し

交通の便が良いので駒込に住んでいたが、昨今の時勢や会社の方針としても最低あと 1 年以上は在宅勤務が続きそうと思い郊外へ引っ越すことにした。

2020年に結婚してからも1人暮らし用の部屋に住んでいて手狭だったのと、隣に引っ越してきた外国人の方々が夜間に騒ぎまくるとか色々環境に限界を感じていて、しかも通勤時間を考慮する必要がないなら都内にいる理由がないよねって感じだった。

今は千葉県の柏近辺に住んでいる。都内と比較すると家賃相場は 3~4 万円程度安い印象があり、前より少し高い家賃を払うだけで(前よりは)けっこう広い部屋を借りることができている。

自分は地方出身(青森) だからかもしれないが、都心のコンクリートジャングルよりは空の広い郊外の方が性には合っている気がしていて、なんとなく近所を散歩していても落ち着く感じがある。人も少ないし静かだ。

デスク周り

デスクワークの疲れを軽減したかったので色々揃えてみた。

昇降式デスクを買ったり

MD770 を買ったり

アーロンチェアを買ったり

それなりに出費は嵩んだが、ウォーレン・バフェット氏も言うように自分の身体は交換不可能であり必要経費だと思っている。

読書

今年のいつ頃からか忘れたが、所属部署のシニアマネージャー(@primunu) の企画した読書会(毎日 9:30 ~ 10:00 )に参加させて頂いている。

こう書くと何人も参加しているような印象を受けると思うが、最終的には自分とシニアマネージャー氏だけの 1on1 的な会になっている。毎日と書いたがお互い欠席は自由な感じで週 3 回ぐらいは同時に参加してるかな?という緩い感じ(参加者が自分だけでも読書する)。各自好きに本を読んだり、自分は本を読みながらシェル芸をしたりする時間になっている。

自分は元々通勤時間で読書していたので、在宅勤務が始まった去年はあまり読書していなかったが、今年はこの会によって読書習慣を取り戻している。今年読んだ本の列挙は別記事でやるかもしれない。

今年 1番影響を受けたのはデジタル・ミニマリスト 本当に大切なことに集中する だと思う。これがきっかけで Twitter の利用時間を 1日 15分に制限したり、その他の SNS はアプリを削除したりして安らかな日々を送ることができている。多分。

ちなみに完全な趣味としては、なぜか司馬遼太郎泉鏡花をけっこう読んでいた。

髪が伸びた

外出自粛も叫ばれていた中で、長髪にすれば散髪に行かなくても良いんじゃないか?と思ったのと、何となく憧れがあり伸ばしてみている。しかし短髪だった人が伸ばす場合は放置で済むわけではなく、定期的に散髪で調整しないとマジで変な髪型になってしまう。結局当初の目的は達成できてないけど、まあ在宅勤務で人に会うことも少ないし長髪もありかと思ってまあそんな感じになっている。

筋トレ

通っていたパーソナルジムのトレーナーがお寺で修行をしたいので退職するという事件発生?をきっかけに、ジムを解約し自主トレに移行。金銭的な負担もまあまあ大きかったし、トレーニングや食事について多少は知識と経験が身についたと思う。契約前と比べると体重 +10kg 前後を達成できている。

引越し後から近所のジムを契約して週 1 回を目安に通っている。週 1 だとトレーニングする部位を絞らないと成果がでにくいのと筋トレしてる感が出やすいのが上半身かな、という感じで胸・肩のフリーウェイトをやっている。

平日夜のジムは混雑して嫌なので、18時前には行くようにしている。17時頃の夕会には運動着に着替えた状態で参加して、夕会が終わったら即退勤して家を出る。駒込の近くのジムや体育館は平日 17 時台でも人がそこそこいたりするが、近所のジムはほぼ人がいないので最高。こういう点でも(少なくとも自分にとっては)都内に居住する利点は少ないと感じる。

ギター

在宅勤務で時間に余裕ができたのと、プログラミング以外に趣味が欲しかったのでエレキギターを始めた。毎日 30 分~練習するようにしている。といっても家に居ない日とか時間がない日もあるので実績としては週 6 ぐらいかもしれない。

練習する上で特に目標とかマイルストーンは設定してない。自分の性格的に目標を設定して達成できないと萎えて止めそうだし、達成のために真剣になりすぎても継続できなさそうだしというのが理由。

練習は毎日30分と書いたが、正確には毎日ギターに触るという部分だけはちゃんと守ろうと意識していて、時間は別に飽きるまでで良いかって感じで運用している。経験上、「やりたいときにやる」と「最低 1分でも毎日やる」は似ているようで全然違うと思っていて、結局ギターさえ持てば 30 分ぐらいはやる生き物だと思うよ、人間は。

完全初心者な上に気軽に相談できる人もいないので当初はスクール通いも検討したが、 YouTube の初心者向け動画がめちゃくちゃ充実していることが分かり、人に会わなくても動画見て自分が実際にやるだけだなって感じがしたので本当に孤独にやっている。プログラミングもそうかもしれないけど、人に教わるというよりは、自分の中に積み上げていくしかない部分の比重が大きいスキルだと思っている。

もうあと何ヶ月かで 1 年ぐらいは練習していることになるが、特にアウトプットとかはなく、1曲通して弾けるものもない。5曲ぐらいの弾きたいフレーズだけを集めて繰り返し毎日チマチマやっている(1曲だと飽きるので)

まあ成長してる実感はあって日々楽しいは楽しいので 5, 10 年後とかにはちゃんと趣味ですって言えるようになれてると良いなぐらいな感じに思っている。

株式投資

前から興味があったのと、よく「エンジニアはビジネスを理解してない」みたいに言われている印象があって、「じゃあ理解してみるか」と思い、積立ではなくあえて個別株の投資(と学習)を始めた。これも詳細を書くと長くなるので別記事でやるかもしれないが、とりあえずできるようになった気がするのは

  • 財務三表の意味が分かる、読める
  • DCF ツールで自分なりに理論株価を算出する
  • 事業の参入障壁を分析する
  • 実際に市場で売買する

など。振り返ってみると結構な時間を株式投資の勉強・分析に使っていたと思う。まあ粗利と営業利益の区別も分からないような状態から始めたにしてはけっこう進歩したんじゃないか。

なお投資成績としては、現時点で含み損。キャリア的に業界が近いのでマザーズの情報通信セクターでポジションを持っているが、今年後半は流動性の低いマザーズ市場全体が嫌気され厳しい状態になっていてツラミ(そもそも日本株自体が対して良くはないんだが、マザーズは更に厳しい)

実際やってみた感想として、個別株の集中投資は究極的にはギャンブルで、どれだけ後付でなにかもっともらしいことを言ったとしても、その時点では人が合理的に予想不可能なことを当てたら勝てるって感じのゲームなのかなって思っている。

短期は市場の予測が不可能だし運なのは当たり前だとして、株価が企業価値に収斂すると言われている中長期(5年~)でやるとしてもギャンブルの色は強いと思ったし、長期でやるなら投資信託でも良くない?ってなった。

そもそも自分が中で働いたこともなければ、社員や経営者と会ったこともなく、使ったこともないようなサービスを売る歴史の浅い会社に対して、決算資料とかだけ見て信用して投資するってなんか根本的におかしくないか?それは投機=ギャンブルだよね。

まあ自分が負けているので批判的な感じになっているのも否めないが、今年である程度の勉強にはなったので、来年は新規に購入はせずに市場を見ながら適当なタイミングで換金しつつ何らかの投資信託へ移行する気がする。

総括

業務時間で自分の市場価値が上がるような学習や経験をさせてもらっているし、在宅勤務で通勤時間がなくなって時間が増えたので自分の好きなことを沢山できた年だった。めでたい。