この技術が分からん2020

2020年に作ったソフトウェアや開発技術をふりかえる で分かったことばかり書いたけど相変わらずなんべん勉強しても分からんな〜と思うことも多いのでそれもリストアップしてみることにした。

SQL

10年以上触っているはずだけど集合のイメージが頭に入ってこなくて全然文を組み立てられずにいる。ゆるふわORMを適当に使ってる。

CSS

10年以上触っているはずだけど制約のイメージが頭に入ってこなくて全然レイアウトを組み立てられずにいる。ゆるふわTailwindCSSを適当に使ってる。

Unity

何回もダウンロードして教材を買ってるんだけど。アセットを組み立てて何か意味のあるものを作るっている状態まで行かない。Flashは使いこなしていたはずなのになぜ

UIデザイン

作る時に一定の理屈っぽいこだわりがあるんだけど、何か自分で作るというところまでいかない上に、深く理由を考えたことすらなかったので、こだわりがあるけど関心が低い。という変な状態だと思う。

VSCode

IntelliJ IDEA派だからなんだけど。VSCodeで使うことを前提とされたツールとかを見て大丈夫かなこれと思ったりする。

GoやRust

最近のシステム言語を身につけた方がキャリアの広がりができてよいとずっと思っているんだけど、何か別の言語で簡単に同じことできるじゃんという邪念が邪魔して真剣に取り組めてない。書けば分かるよ? 書けばな〜。必要性がな〜。と言い訳している

ネットワーク

サブネットとかルートテーブルとか概念は分かるがいつもはい…はい…と念じながら整えている感覚があり気持ち悪い。

数学

ありがち。必要→納得→興味→関心のパイプラインにすら到達していない気がする。

競プロ

アルゴリズムコンピュータサイエンス、数学ぐらいの具象化されたものより抽象的なものを求めているのか楽しさが分かってなくてのめりこめずにいる*1

あと単に基礎知識が不足している。

英語

英語がしゃべれないと死ぬ(比喩)ような環境に飛び込んでたらいける論があるけど普通に生活に支障が出ているし生存バイアスから漏れてしまっている。

人間の気持ち

オライリーブックス『詳解 人間の気持ち』が欲しい

そもそも——

プログラミング自体が数年がかりでできなかった。同時期にネットをはじめた知人がPerlCGIテキパキ組んで仕事をしているのをうらやましく見ていた。

その後、数年わからん続けて本屋で 改訂版 FreeBSD徹底入門 を買って読んでみたけど全然わからなくて読むのをあきらめた。

そのまた数年後 みるみるプログラミングがわかる本 を読んで壁を超えた気がする。この本自体が初学者にすごくおすすめというわけではなかったんだけど、特徴ある点としてはプログラミング言語とは言語の違いではなくて開発ツール(IDE)の違いですってことが書かれていたと思う。現在も開発環境おたく気質があるからそこが性に合っていたんだろ思う。

あと、自分は高等教育を受けることに失敗しているので、独学してみて分からなく挫折する状態に慣れているんだと思う。良い面もあるし悪い面もありそう。

分からんのスコープ

ソフトウェアで何かやりたいことがあるとして

  • やり方を知らべる
  • ドキュメントを読む
  • 実行する

という行動が取れる自信がある時は自分はそれを「できる(できそう)」にカテゴリしてるんだけど、他の人と話していたらそれは実績がないから「できない(やったことない)」に区分されるという意見を聞いてそうなんだと驚いたことがある。

それ以来は、分からないけどやったらできそうではある。となるべく正確に答えることにしている。

*1:https://b.hatena.ne.jp/entry/4695815452087964034/comment/redreborn を読んで思ったけど抽象的という喩えよりいい言い方があるのかもしれない

2020年に作ったソフトウェアや開発技術をふりかえる

概要

よくある年末っぽい日記の記事です。

だいだいこれどうりのバランスでソースコードも書いてる

  • 言語はなんでもいい時はNode.jsで書く。移植性が高いので。複数人でメンテしそうな時はTypeScriptを採用し、プライベートの時は型を完全に無視する
  • PHPはほぼLaravel。ビジネスのみの関係
  • Swiftはそんなに書いた記憶がないけどアプリのメンテをしてたと思う
  • Vueも仕事で使っていたけど最近はReactに傾いてる
  • Objective-Cは書いてない
  • グラフに含まれてない部分だとAndroidアプリでKotlinを使って、データ分析でPythonを書いた

このグラフは GitHub Profile Summary Cards っていう便利ツールを使わせてもらって自動生成している。

記録方法

  • コードを書く時はおもむろに ~/tmp 以下にディレクトリ掘ってIDEを開きはじめるので実質そこがプロジェクトサンドボックスみたいになってる。
  • なのでその場所を時系列に並べてチェックしていくことで何をやっているのか分かった。
  • あとはGitHubとかTwitterとかに記録が残っていた。

フロントエンド

ReactやVueに本格入門した年だった。

Nuxt

SPA+外部APIアクセスで開発するのに最適なウェブアプリケーションのプロジェクトがあったので採用した。

SSRが不要なのでvue-cliを使うか迷ったけれど、コミュニティの充実ぶりを見てNuxtにした。

複数人でメンテする可能性もあるので最初からTypeScript化した。苦心したのはStore層であんまり型が生かせてない書き方しちゃったり、vue/test-utils を使ったユニットテストがうまく構成できずにVueコンポーネントでラップしてテスト書いてたという部分。こういうのって他所の開発チームに実際入ってみないと作法がよく分からないなと感じた。

Amplify

外部APIAWSで動いてて、それにあわせてデプロイ先をAWS Amplify コンソールにしたがこれがすごい楽でよかった。また使いたい。

いっさいドキュメント系読まないでも、よくある既存のPasSのあの機能みたいなものね〜という感覚ですんなり運用できた。

Amplifyのバックエンド系の機能は全然使ってないので、そこは別の学習コストがあるのかもしれない。

Next

DBに数千件ある既存のコンテンツのページを生成する、各ページもSPAとして動的な機能を持つっていうのを作っていてv10.0が出て丁度良さそうだったNextを使った。

最初はFirebase Hostingへgenerateしたhtmlをアップロードしていったんだけど、ビルド時間の膨大化問題に悩まされてこれはSSRが必要になってくるなと思いVercelに移行した。

Vercelに移行してSSRを併用しはじめたらNext印象がまた変化しました。魅力をひとことで説明するのは難しいのだけど「いかに静的に確定する部分を増やして、それを CDN に置くか」 っていう文章を読んでそれや! としっくりきてしまった。

アプリケーションを書く視点で見ると、サバクラ2階層MVCの手続き型のコードだった部分が丸ごと宣言型のコードになるのでだいぶシンプルになったように感じました。

ちょうど2014年ぐらいにReactをはじめて使った時のDOM操作の複雑な手続きが一気になくなった感覚がウェブアプリケーション全体に拡張されているかのようです。

Gatsby

Nextのプロジェクトと似た構成で、数千コンテンツのページを生成するツールを作ってた。こっちはJAMstackという言葉に踊らさせていた時期だったのでNetlifyにデプロイしていた。

HerokuのPostgreSQLをパブリックで空けてデータを突っ込み、Netlifyのビルドサーバーから gatsby-source-pg 経由で吸う。という構成にしていた。

GraphQLの参照系のパワフルさはこのGatsbyの利用で体験できた。最終的にはもしろGraphQLフレンドリーになるようにポスグレのスキーマ側を調整したりしてた。

Gatsbyもnext buildと同じく最終的にはビルド時間の最適化との闘いになりそうだなと思った。Gatsby社のプラットフォームにインクリメンタルビルドの仕組みが提供されているけど。自力でそれを構成するには骨が折れそう(できる人は別のアプローチも取れそう)、という部分もVercelと似ている。

Laravel

はじめてまともに使ったけどRails並によくできていた。

強いて言うならPHP以外だとよかった。が、Symfony Components にもとづく物であろうのでPHPでなければ駄目だった。ありがとうPHP

PHP自体も久しぶりに書いたけどあんまり変わってなかった。ウェブに出回っている利用例の情報量は多いのだけど、Kubernetes環境+nginxの組合せで使っている人たちがあんまりいなかったのでインフラ構築に苦労した。

Viewは時代に逆行してSPA完全無視でbladeとlaravel-admin活用して作ってる。その方が目的が早く達成できそうだったのでそうした。

Helm

2019年はKubernetesとKustomizeに入門したので、今年はHelmfileでHelmパッケージとしてシステムをデプロイできるようにしてみた。2019年に導入しかけて、難しいなーと思い見送った時よりウェブ情報が充実していたのですんなり理解できて良かった。

Rails

3.x系のまま誰にもメンテされなかったアプリケーションを引き取り、サーバーの体調が悪化した時に救急手当をしばらくしていたんだけど「これ? Dr.コトーじゃん・・」と思ったのでSlackのアイコンをDr.コトーにした(とくに意味はない)

2020年まで精一杯生きて静かに息を引き取った。

Python asyncio

クローラー開発でPythonを使うことを決め(参考実装がPythonだった)、素朴なPythonブロッキング処理を書いていたんだけどそうすると長時間化していって動作環境のGitHub Actionsの従量課金に苦しめられることになったので並列実行を検討しはじめた。

今どきのPythonはasync/awaitでノンブロッキング処理書くぞと噂で聞いていて知っていたので挑戦してみた。Node.jsで慣れてるのですんなり使いこなすことはできたけど、どうしても外部ライブラリが非同期処理に対応できていなくて最終的にマルチプロセス+スレッドプール+asyncioで非同期と多重にしたら現実的なラインまで改善したので良しとした。

この過程でGitHub Actionsを月4000分以上使うなら1人チームのGitHub Enterpriseを申し込んだ方が安い。というニッチなtipsを得た。

自然言語処理機械学習

Pythonプログラミングのリハビリをして気をよくしたので、かねてからの懸案だった「数理的な知識をつける努力をせずに機械学習ツールを使いこなせるようになりたい」という実現に乗り出すことにした。

データ収集、前処理、トレーニング、テストなどの必要な手順の概念と、代表的なアルゴリズムチュートリアル。sklearn,gensim,kerasなどの組込み方を覚えたので。他人からアドバイスを受けて何か実践する。ぐらいのことができるようになったと思う(自分でウェブ検索しても出てこないような問題のアプローチを1から考える、というのは難しい)

Android

Jetpack Compose

ちいさなアプリを書いてだいたいどういう感じか把握した。

[Jetpack Compose] Glide経由でコンテンツ一覧を表示してタップされたらブラウザで開く

SwiftUI版も作ったと思うだけど、コードが残っていなかった。何も思うところがなかったのだろう

トルコ語アルファベット

トルコでのみアプリが落ちるという不具合があって、調べてみたらKotlinのenumJavaのStringに変換する処理で端末のロケールが使われトルコ語アルファベットになり正常に動作しないという現象が起きていた。

トルコ語アルファベット自体をこの時はじめて知ったので勉強になった。

Flutter Engine

Flutterは明かに今までのモバイルクロスプラットフォームとはアーキテクチャが違うので、その内部構造から理解したいなぁと思い。C++で実装されたエンジン部分のソースコードをチェックアウトして動かしたりしてみた。

Dartの部分を別の処理系に置き換えるには何が必要? という観点で読んでいた。1-2月に取り組んでてだいぶ間が空いてしまったのですっかり今は忘れてしまったけど。DartVMからSkia命令を叩く一連の流れを把握したと思う。

Mac DE Xamarin

Covid-19 Radar 関連でひととうりVisual StudioやらXamarin SDKを揃えたんだけどもう残ってない。

mhlwリポジトリは今見てみたらコミットが積まれていた。やるじゃんパーソル

https://github.com/cocoa-mhlw/cocoa/commits/master

Hiraganize

ウェブページを全部ひらがなに変換してしまうiPadのウェブブラウザ。漢字を読めない人とかに便利だと思う。

expo(React Native) + kuromoji.js でJSベースでテキスト変換処理をがんばっていたがモバイル端末で動かすには重過ぎたので途中からlibmecabとSwiftで書き直した。

ネイティブアプリの強みを生かしてSafari Actionにして、アプリ起動しなくても今見てるページをSafari上でそのまま変換するってところまで作った (https://twitter.com/laiso/status/1225438961738235905)

その後、漢字重要だよねということに気付いたので漢字は残しふりがなをふるブラウザに拡張してみた。

今は放置してる

プリント検索アプリ

私立小学校というアナログ推進団体と利害関係を持っているんだけど、彼等が毎日毎日 大量のミッションクリティカルなA4書類を配布してきて毎回紛失して困っていたので電子化して検索できるようにした。

これには ionic というフレームワークを使ってまずAngularベースのウェブアプリケーションを構築して

  1. 写真を取ってCloud Storageに上げる
  2. Cloud FunctionsでCloud Vision APIを叩いて文字情報を読み取る
  3. 文字情報をAlgoliaでインデックスして検索可能にする

という設計にした。ついでにオンブラウザでJPEG -> PDF変換機能も実装してみた。

ionicのいいところとしてはウェブアプリケーションとして作ってみて、その後モバイルネイティブAPIが必要になった段階でフォールバック的にWebViewでラップしたアプリに拡張できる点だと思う。

Angularが第一級市民だったので使ったけど今はクロスコンポーネント化されてReactやVue3向けのインターフェイスも提供されている。

note関連ツール

unnote

https://github.com/laiso/unnote

なんとなくこんな検索結果になったおもしろいなと思って作っていたやつ

ウェブサービスとして提供している人がいるのでそれを使うのがいいと思う。

noteのバックアップ/エクスポートファイルを無料で作成 - noteエクスポートβ

md2note

Electron下からnoteの非公開APIをNodeで叩いてパースしたMarkdownファイルで無理矢理記事を作成しちゃうやつ。

汎用的なツールにできないので自分だけ使っている状態……

findlist

https://gist.github.com/laiso/85b6e8666569a0ed6c86391f70e783cc

Twitterの非公開リストのパーミッションが壊れる障害があった時に、連番で探索するスクリプトを組んでた(いい話)

ここをこう持つ

歴代Xcodeのダウンロードサイズ

Xcodeがデカ過ぎることにむかついて作った

Qiitaからはてなブログにデータ移行するコード

Qiitaからはてなブログに記事を一括インポートする - Qiita

LINE BOT経由でYoutube動画をダウンロード

ペアブラウジング

なんとなくこういうツールが作れそうだと思ってやってみたけど用途はよく分からない

まとめ

  • 環境に恵まれているのもあってとにかくずーっとコード書いてた
  • 一方プロジェクトにはすぐ飽きてしまう。やりきる力が来年の鍵
  • 生活や健康には悪影響があると思うのでほどほどにできたらいいなと思う
  • あとこういうプログラミングが趣味な人は学習コストを低く見誤る傾向があると思った
  • この記事を自分で読んでて思ったけどこのまま行くと「町の変人発明家」ポジになってしまうのでは????

個人開発者とCovid-19 Radarプロジェクト

f:id:laiso:20200622042515j:plain
Endless road | During our roadtrip we turned off the highway… https://www.flickr.com/photos/98063470@N00/326044514

GitHubリポジトリ Covid19Radar に対して起ったことがかなり特殊な状況だったため、開発を追い掛けていた視線からレポートをします。

この記事の著者について

  • 代表作のない個人アプリ開発者(かなしい)
  • Covid-19 Radar Japan の人ではない
  • GAFAMやCode for Japan の人でもない

4/8 Covid-19 Radarを発見する

  • Covid-19 Radarとは、この時点ではシンガポールのTraceTogetherの日本版を目指した個人開発者 廣瀬一海さんのアプリのリポジトリ
  • 4月にContact Tracing技術について調べていたら偶然見つけた

  • 4/8時点で人が集っていて活発に開発されていた。Facebookで協力を呼び掛けていたらしい*1
  • sinsai.info みたいなムーブメントかな。ぐらいの認識だった

4/10 Apple/Google APIの開発が発表される

AppleとGoogle、 新型コロナウイルス感染症対策として、 濃厚接触の可能性を検出する技術で協力 - Apple (日本)

  • この時点では発表だけ。ベータリリースされたら使ってみるかぐらいの感想だった

4/15 コード・フォー・ジャパンもContact Tracingやってるらしい

5/5 APIを使えるアプリは各国1つのみ

  • ここで個人開発者オワタと思った

6/13 突然Covid-19 Radarが日本代表になっていたことを知る

  • コード・フォー・ジャパンのプロジェクトどうなったの? とかあれは個人プロジェクトでは? という疑問があった

6/15 開発が米マイクロソフトに移った??

  • 日経新聞の飛し記事が出る

  • 「「コード・フォー・ジャパン(CFJ)」にアプリ作成を任せる方針」が覆った理由が全然わからない
  • 5/11に投稿されたらしい 接触確認アプリ「まもりあいJapan」開発の経緯と今後について を読むと開発主体がCFJから厚生省に移ったことが要因らしい
  • Covid-19 Radarの作者はたしかに日本マイクロソフトの人だったから、会社を巻き込んだプロジェクトにしたのかなー、と思った
  • ちがうらしい

  • 今思うと厚生省で「大企業のほうが安心だ」と意思決定したおじいちゃんも「米マイクロソフトに発注したぞ!」と本当に認識していたのかもしれない

6/19 アプリがリリースされる

  • 開発者インタビューなどが世に出る
  • GitHubリポジトリだけを見てるとチームで開発してる感がなかったのでどんな開発体制なんだろうと疑問だったんだけど https://diamond.jp/articles/-/240905 でデザインやリレーションを担当している人が存在することを知る。あれ、開発は本当に1人でやっているの??

GitHubリポジトリを見始める

  • 日本初のApple/GoogleのContact Tracing APIを使ったアプリが出たぞということで技術的な関心駆動でコードを読み始める(Xamarinわからん過ぎるのでとりあえずチェックアウトだけして中身見てなかった)
  • この時点でいくつかのことが分かった

  • アプリサイドを廣瀬さん、サーバーサイドをdarkcrashさんが中心に開発している

  • runceelさんalbilagaさんなどがXamarin系の修正プルリクエストを送って手伝っている
  • norijiさんが前述の記事で出てくるデザイン作業で貢献している方
  • changeworldさんという人がもの凄い勢いでIssueやプルリクエストを捌いている

過去にAd-Hoc版ビルドのテスト版が配布されていたことを知る。デバイス登録上限に達するぐらい参加者がいたらしい(注: このアプリは5月末にリリースされたiOS 13.5にアップデートした端末か事前のiOSベータ版配布を適応しないと動作確認すらできない)

iPhone のテスターの方へ

この時の配布ツールApp Center用のライブラリが本番バイナリに入ってデータ送信していたらしく、mala(呼び捨て)が発見して取り除かれた。

Getting Started

  • アプリをビルドしてみて修正しようと思ったけど全然やり方がわからない
  • 開発環境構成ドキュメントはまだ存在しなく、なんとなくGitHubの外にチャットなどがあって、そこで開発が進んでいるのかな〜と思いはじめる
  • ふて寝する

6/20 なんとかなったぜ

  • いろんな方法を探っていたら結果うまくいってコードを編集して動作確認できるようになった
  • iOSのビルド設定が廣瀬さん固定になっていたので誰でもビルドできるような変更をプルリクエストしてみる

iOS Bundle SigningOption to 'Automatic' by laiso · Pull Request #471

  • ここでなんとApple/Google APIのデバッグは廣瀬さんの環境でのみしかできないことが判明(たぶん)。超SPOFじゃん・・と震える

(カルチャーフィットしなさそうなので厚生労働省には入らないことにしました)

結局Exposure Notification APIをモック化する仕組みが既に用意されていて(有能)それを使うことにした。

About messages when sharing the app on line

「LINEでシェアすると表示がおかしい」というIssueを見たので検証して直してみる

change appStoreUrl to valid URL by laiso · Pull Request #474

「俺の修正のおかげで直ったわ〜」と達成感を味わっていたらmala(呼び捨て)が出てきてLINEのサーバーで修正されてしまう(チートでは??)

Line breaks not works in a mail app

  • iOSのメールアプリでお問い合わせするとエスケープ済みの改行コードが入るらしい
  • メール改行テキストの修正のパッチを提案する(普通にググってうまくいくか試しただけ)

Line breaks not works in a mail app · Issue #479

この問題翻訳ファイルの元の問題を修正する必要あるんだけど、翻訳ツールがWindowsでしか動かないことを知る(Macの人居なかったのか・・!)

App crashes

  • App crashes · Issue #450 が再現しやすいので修正を試みる
  • ソースコードをいじって検証してみたけど前述のとうりSPOFで世界がヤバいので僕の環境ではデバッグすらできなく、直ったのかどうかわからんけどとりあえずプルリクエストにしてみる

fix a crash on StopExposureNotification() by laiso · Pull Request #484

  • このあたりで前述のApp crashes Issueのやりとりを見た人たちが「公共サービスをこのような認識で開発されているのはいかがなものか?」と騒いでるらしいことを知人に教えてもらう
  • 僕は彼等に対して距離を置くことで軽蔑やNOを示したいので、透明なおじさんは見えませんよみたいな感覚で見なかったことにした(不具合の事実の報告は重要なのでチェックだけする)
  • とはいえ当事者の人たちはダメージを受けていたらしく「SPOF〜〜」と見守っていた

Xamarinについて

  • 知人iOSエンジニアなどが「コントリビュートしたいけどな〜技術スタックがな〜Xamarinがな〜」と嘆いているのを見かける
  • たしかにXamarin+Mac+iOSは少数派ゆえ茨の道っぽい……

これまでの流れで思ったこと

  • 国の意向で日本代表の感染症対策アプリ開発が個人開発アプリに丸投げされたっぽい(そんなことあるの??)
  • 関係者の発言(公開された情報)によるとリリース日が事前に決まっていたらしい
  • Covid19 RadarはMPL v2.0に基くソフトウェアだけどプロジェクトは一般的に知られる「GitHubで開発が行なわれているOSSプロジェクト」ではなく個人開発者とその周辺のボランティアだった
  • GitHubリポジトリがとりあえずある(元が個人開発アプリなので)。というだけの状態
  • 今後このプロジェクトは厚生省及び開発委託先によって管理されるらしい
  • 「大企業のほうが安心だ」と意思決定する人がこのGitHubリポジトリを維持するものなのか
  • userUuid および secret の廃止 · Issue #514 のような提議をできる場はあるのか
  • 現在改善が進んでいるMPLのコードベースがどうなるのか
  • 人類がはじめて直面する事態に対してアプリケーションのプラットフォームが特例的に出した仕組みを使って、1つの国で1つのアプリを1人が作る*2異常な状況なので何が正解なのか誰も分からない……
  • 廣瀬さん関係者の方々お疲れさまでした

06/24/2020:この記事を投稿した後にわかったこととか

高木浩光さんの発言の反応について

  • App crashesのIssueは高木さんの投稿(RT?)から話題になっていたのだと後から知った
  • インストールしてセットアップ完了をさせる人数が重要で、それが生死に関わる問題のアプリケーションなので、感情や責任に対する議論に終始して「たいした問題じゃない」という言葉で弁護するのは賛同できないなと思った
  • 自分なら「クラッシュの回避はとても重要である」という意見になる。最新の発言しか読んでないけど高木さんも似たような指摘をしていた
  • そもそもフレームワークレベルで呼んだら捕捉できる例外を返して欲しい
  • あとあれは正常系だと思う(用語の話)
  • そして他に指摘されていた不具合はリリース当日の既にmasterブランチで修正されているものもあり(日付表示の問題など)、v1.0.0はその時点のスナップショットが世に出された印象だった(言いたいこととしてはフィードバックの修正が進んでいる)

HER-SYS(新型コロナウイルス感染者等情報把握・管理支援システム)

  • 接触確認アプリはHER-SYSとの連携を予定している(陽性者登録のシステムなど)
  • 接触確認アプリの開発はHER-SYS開発の契約の一部である
  • HER-SYSの発注先がパーソルプロセス&テクノロジーである

これが知りたかった情報にもっとも近かった(楠正憲さんは内閣官房情報化統括責任者補佐官でもある)

Covid-19 Radarへ

  • 接触確認アプリはパーソルプロセス&テクノロジー以下へ委託される
  • HER-SYSの基盤はMicrosoft Azureだった *3
  • Covid-19 Radarも4月時点でAzure+.NET+CosmosDBを利用してAPIが実装されていた
  • まもりあい JAPANのAPIサーバはAWS+Node.js+Firestore*4 で実装されていた
  • Serverless FrameworkのプロバイダをAWSからAzureに移行する。これは互換性の面では可能だがFirestoreは維持するとなるとマルチクラウド構成になってしまう
  • まもりあい JAPAN以外にもContact Tracingアプリ開発プロジェクトはあったし、スクラッチから開発するという選択肢もあった
  • パーソルプロセス&テクノロジーが「アプリ開発」の部分の再委託先を決めないといけない。それには時間がかかる
  • リスクを減らすには技術的な相性を加味する必要があるのではないか
  • (それ以外にも採用してるライセンスや開発担当者の身元など様々な要素があると思うけど……)
  • そして再委託先の決定+開発開始のリードタイムを埋めるべく作業を任されたのがCovid-19 Radar Japan?(ここまでは勝手な推測)
  • なので「無報酬で依頼を受けるべきでなかった」は一面的な見方だと思う

こういうこと?

  • 政府にとって第1イシューは「早期にリリースする」ことだった
  • 早期にリリースするために各自が出した答えが現在の状況と体制
  • 早期リリースは実現された

今後

  • 完全にメンバーが変わるのなら、バックエンドはまだしもアプリ部分はネイティブ実装(Swift/Kotlin)に置き換えられていく可能性は充分あるなと思った(そちらのが信頼のおけるサービスになるのなら)
  • GitHubリポジトリの運用も維持される可能性はありそう(他国事例や東京都サイトに続けて政府のIT施策アピールにもなる)。ただ委託先との契約上どうなっているのか次第なのかもしれない

お詫び

  • 文章を読み返してみたら厚労省やXamarinに対して貶めるような表現が多くバランスを欠いていたなと反省しました。謹んでお詫び申し上げます
  • 記事のコメントを読んでいたら著者について、実態よりCovid-19 Radar Japanのインサイダーっぽく受け取られているなと感じました。とくに開発に積極参加しているわけではなく、メンバーと交流があるわけではないのであくまでも部外者の視点であることを強調します

どんなプログラミング技術の学習に投資すべきか考える時にやってること

はじめに

とにかく次の10年を生き残りたい - 怠惰を求めて勤勉に行き着く を読んでいて、かー自分もここ10年ぐらい同じような内省をし続けていたではないかと深く共感したので、その過程で身に付けたやり方を書くことにしました。

目的

プログラミング技術を学習する目的を決めます。僕の場合は

  1. ソフトウェアエンジニアとしての市場価値を上げる(他のエンジニアから尊敬されたい)とか
  2. 素晴しいアプリケーションを作れるようになりたい(エンジニア以外からも尊敬されたい)

というものがあります。人によってはこれが「GAFAMNに入り渡米してメジャーデビュー」「OSSで一発当てる」「とにかくお金を稼いでアーリーリタイヤ」など様々かと思うので各自考えてください。

テーマ

目的が決ったら次は学習するテーマを決めます。僕の場合は「○○エンジニア」と呼称されている領域ごとに「この分野で先進的なネタは何だろう」というのをリサーチします。テックニュースやGitHubトレンド、口コミ、求人情報などから探します。

それを視覚化します(箇条書きでもなんでもいいです)、僕はMarkdownで下書きして、マインドマップにしました。

f:id:laiso:20200110000247p:plain

大テーマである「○○エンジニア」の部分は人によって違うはずです。

僕は素晴しいアプリケーションを作れるようになりたいので適切な技術を選定できるように分野を広めに保っているのだと想います。

たとえば「Netflixで英語学習するツール」を実現したい時、ブラウザ上で動くNetflixのサイトにデータと機能を組み込んで連携させるのが一番良い方法だな、と思ついたとします(実際にそういうツールが存在します)。

これにはウェブアプリーケションが動作する仕組みとブラウザ拡張についてのフロントエンド開発の知識が必要になってきます。

ところがiOSアプリの開発スキルだけあってもこのアイデアを実現できたりはしませんし、そもそも方法を知らないと思い付かないかもしれません。逆にネイティブアプリでしか実現できないことが手持ちの知識の中にないとそれによって作るものが制限されてしまうこともあるかと思います。

しかしなるべくなら枝は少ない方が好ましいと考えています。枝を減らすコツは「別の技術で同じものを作れる」状況をなくすことです。例としてはRubyでもPythonでもWebサーバーは作れますが、SwiftでないとiOSアプリは作れないので、増やすべきはSwiftです。

ここに出てくる各固有名詞の製品自体を作りたい人はもっとCSの基礎技術を拾うべきでしょうし、数学、デザインや語学、データサイエンス関連も含める人もいるかもしれません。

先進的なネタの基準

ソフトウェアエンジニアとしての市場価値を上げるという目的があるので、「先進的なネタ」というのは今現在の業界で起っていることで次の時間軸にあるものを選びます。

たとえばRuby on Railsを使ってWebサービスを作っている企業があり「今後はマイクロサービス化しGo言語に移行したい」という動きがあるとします。それを見据えた時に学習のネタとなるのはマイクロサービスだったりGo言語だったります。無事これらの技術を習得する頃に、世の中に「マイクロサービス化してGo言語に移行したプロジェクト」の総数が増えていれば、需要も上っていてエンジニアとしての市場価値も増すので投資のリターンを得てやったねというわけです。

テーマに優先順位をつける方法

大テーマである「○○エンジニア」の部分は数が多ぎ過ぎなので、どれを優先させるべきかというのを考えます。

ここでの基準は

  1. 普段の業務でやらない分野
  2. 業務でやる分野だが自分の担当ではない
  3. 普段の業務でやっていること

の順で優先付けします。

これは「適切な技術を選定できるように分野を広めに保つ」という考えからまんべんなくステ振りできる方法として考えました。

「それぞれの分野の理解が浅くなってしまうのでは?」という器用貧乏症候群については、こう考えます

  • 普段の業務では深い理解を求め
  • 学習フェースでは広い理解を探索する

こうすることで効率よくハードスキルのT字の延せるのだと考えています。

いかがでしたか?

そもそも私はどの技術的なトピックが将来発展するのかという予測は不可能だと思っています。現実の世界では常に変化し続ける環境の要因が複雑に絡みあっているからです。

なのでビジョンを持って、常に変化し続ける環境に適応する(あとは運)。というやり方がしょうにあっているのだとソフトウェアエンジニア人生で学びました。

みなさんも独自の方法論を教えてください

Cloud FunctionsをGoで書く。またはFirebaseのためのマイクロサービスアーキテクチャ

f:id:laiso:20191216155312j:plain

Firebase Advent Calendar 2019 の17日目です。16日目はKesin11さんの「Firebase Emulator Suiteをフル活用してTDDで開発しよう」でした。

はじめに

FirebaseプロジェクトでCloud Firestoreを利用する時は通常Node.jsによるCloud Functionsでトリガーとなる処理を記述します。その他には関連するAPIサーバー、WebアプリのフロントエンドのSSR、バックエンドの非同期処理など、多くの場面でCloud Functionsが活用されています。

この開発→デプロイサイクルをお手軽に行ってくれるのがfirebase-toolsというnpmモジュールです。JavaScriptでFunctionを実装し、firebase deployコマンドを実行するだけでFirebaseプロジェクト用のCloud Functionsが自動で登録されます。

解決したい問題

firebase-toolsは便利に使っていたのですが、私たちのプロジェクトでは日々いくつものCloud Functionsのモジュールが増えてゆく過程で以下の問題が発生しました。

  1. デプロイ対象となるソースコードの量が増え、デプロイ完了までの時間がかかるようになった(--onlyオプションを付けても遅い)
  2. SSRを含むフロントエンド向けビルド、APIサーバーとして機能するFunction、Firebase固有の処理、などの依存が単一の package.json で管理されていてモジュール更新の影響が大きかった
  3. 複数のFirebaseプロジェクトでプライベートリポジトリにある自作モジュールを共有したかった
  4. Cloud Tasksや複数Functionへの分散した水平スケールでは実現できない、リアルタイムの大量データ処理を垂直方向にスケーリングしたかった

そこで私たちはまずはじめにfirebase-toolsの仕組みやCloud Functionsがどのように動作しているのかを理解して、最終的にfirebase-toolsで構成していたFunctionのビルドを徐々にマイクロなモジュールに分割していくことにしました。

Cloud Functions for Firebaseの仕組み

Cloud Functions for Firebase*1

Cloud Functionsは - リソース - イベント - 関数名

という組合せでFunctionを管理しています。特定のリソースAに発生したイベントαにアップロードしてビルド済みのプログラムから指定の関数を実行します。

/**
 * [Google Cloud Firestore トリガー](https://cloud.google.com/functions/docs/calling/cloud-firestore?hl=ja)
 */

const Firestore = require('@google-cloud/firestore');

const firestore = new Firestore({
  projectId: process.env.GCP_PROJECT,
});

exports.makeUpperCaseOne = (data, context) => {
  const {resource} = context;
  const affectedDoc = firestore.doc(resource.split('/documents/')[1]);

  const curValue = data.value.fields.original.stringValue;
  const newValue = curValue.toUpperCase();
  console.log(`Replacing value: ${curValue} --> ${newValue}`);

  return affectedDoc.set({
    original: newValue,
  });
};

という関数を持つ index.js のみのコードを用意して

# package.json を生成する
npm init && npm i @google-cloud/firestore

gcloud functions deploy makeUpperCaseOne --runtime nodejs8 \
  --trigger-event providers/cloud.firestore/eventTypes/document.create \
  --trigger-resource "projects/$GCP_PROJECT_ID/databases/(default)/documents/messages/{docId}"

というコマンドでデプロイすると(gcloudコマンドは別途セットアップします)

projects/$GCP_PROJECT_ID/databases/(default)/documents/messages/{docId} のリソースに対して providers/cloud.firestore/eventTypes/document.create というイベントが発生した時に 関数 makeUppercaseOne() をトリガーする。

というCloud Functionsのリソースが登録されます*2

試しにコンソールからFirestoreのドキュメントを作成してみます

実行されドキュメントが更新されました。

同等の処理を行うFunctionをCloud Functions for Firebaseで実装してみることにします。

以下のようにfirebase-functionsモジュールを使ってFunctionを定義すると

const functions = require('firebase-functions');

exports.makeUppercase = functions.firestore.document('/messages/{documentId}')
    .onCreate((snap, context) => {
      const original = snap.data().original;
      console.log('Uppercasing', context.params.documentId, original);
      const uppercase = original.toUpperCase();
      return snap.ref.set({uppercase}, {merge: true});
    });

firebase deploy --only functions の内部で先程のgcloudコマンド同様のリソースができるわけです。

gcloud functions deploy makeUpperCase --runtime nodejs8 \
  --trigger-event providers/cloud.firestore/eventTypes/document.create \
  --trigger-resource "projects/$GCP_PROJECT_ID/databases/(default)/documents/messages/{docId}"

元のFunction用のコードからソースコードをそのままに1つの関数だけを抽出して、フォルダ構成としてはこのような独立したnpmパッケージとして管理できるようになります

functions/makeUppercase/
├── index.js
├── node_modules/
├── package-lock.json
└── package.json

Goランタイムへの置き換え

Cloud Functionsへ直接デプロイすることができたので、次にFunctionをGo言語で実装してCPUメモリあたりの実行速度の高速化を図ります。ボトルネックがI/Oではない典型的なデータ処理のFunctionはこれだけでスループットが向上が期待できます。

var projectID = os.Getenv("GCLOUD_PROJECT")
var client *firestore.Client

func init() {
    conf := &firebase.Config{ProjectID: projectID}

    ctx := context.Background()

    app, err := firebase.NewApp(ctx, conf)
    if err != nil {
        log.Fatalf("firebase.NewApp: %v", err)
    }

    client, err = app.Firestore(ctx)
    if err != nil {
        log.Fatalf("app.Firestore: %v", err)
    }
}

更新した値をFirestoreに書き込み更新する場合、Firestore Clientの準備をします。

init() はランタイムにより指定されているインスタンス毎の初期化処理で、Node.js版でいうグローバル変数にセットアップ済みの値を入れておく方法を似ています。

type FirestoreEvent struct {
    OldValue   FirestoreValue `json:"oldValue"`
    Value      FirestoreValue `json:"value"`
    UpdateMask struct {
        FieldPaths []string `json:"fieldPaths"`
    } `json:"updateMask"`
}

type FirestoreValue struct {
    CreateTime time.Time `json:"createTime"`
    Fields     Message    `json:"fields"`
    Name       string    `json:"name"`
    UpdateTime time.Time `json:"updateTime"`
}

type Message struct {
    Original struct {
        StringValue string `json:"stringValue"`
    } `json:"original"`
}

func MakeUpperCaseGo(ctx context.Context, e FirestoreEvent) error {
    fullPath := strings.Split(e.Value.Name, "/documents/")[1]
    pathParts := strings.Split(fullPath, "/")
    collection := pathParts[0]
    doc := strings.Join(pathParts[1:], "/")

    curValue := e.Value.Fields.Original.StringValue
    log.Printf("Uppercasing: %q", curValue)

    newValue := strings.ToUpper(curValue)
    data := map[string]string{"original": newValue}
    _, err := client.Collection(collection).Doc(doc).Set(ctx, data)
    if err != nil {
        return fmt.Errorf("Set: %v", err)
    }

    return nil
}

Function本体の処理です。Go言語の型システムの仕様上Firestore内の値のパース処理を、Node.js版でいう firebase-functions にあたるユーティリティがないためドキュメントパスの解決などを自分で実装する必要があります。

デプロイします。

gcloud functions deploy MakeUpperCaseGo --runtime go111 \
  --trigger-event providers/cloud.firestore/eventTypes/document.write \
  --trigger-resource "projects/$GCP_PROJECT_ID/databases/(default)/documents/messages/{documentId}"
func TestFunc(t *testing.T) {
    var projectID = os.Getenv("GCLOUD_PROJECT")
    jsonStr := `{
      "original": {"stringValue": "hello"}
  }`
    var message Message
    var err error
    err = json.Unmarshal([]byte(jsonStr), &message)
    if err != nil {
        log.Fatal(err)
    }
 
    value := FirestoreValue{
        Name:   "projects/" + projectID + "/databases/(default)/documents/messages/1",
        Fields: message,
    }

    result := MakeUpperCaseGo(context.Background(), FirestoreEvent{Value: value})
    if result != "HELLO" {
        t.Error()
    }
}

動作確認をGoのユニットテストとして記述できます。

写真のリサイズFunctionをGoで実装してみる

Functionのイメージ*3

ユーザーがアプリケーションから写真を登録したらシステムで必要なサイズの画像を自動で生成するようなFunctionをGoに置き換えてみます。

  1. Document更新トリガで関数を実行
  2. パースしてきたURLから画像をダウンロードしてくる
  3. リサイズを実行
  4. 画像をCloud Storageに保存

という一連の流れです

conf := &firebase.Config{ProjectID: projectID}
opt := option.WithCredentialsJSON([]byte(os.Getenv("SERVICE_ACCOUNT_JSON")))
app, err := firebase.NewApp(ctx, conf, opt)

Firebase Admin SDKの初期化時にクレデンシャルを含むJSONファイルのパスを指定するのではなく、環境変数から読み込むようにします(confも同じ形式にできるのですが、秘密情報を含まないためコードで指定しています)。

gcloud functions deploy Resizing --set-env-vars SERVICE_ACCOUNT_JSON=$(cat secretkey.json)
// var fbStorage *storage.Client
fbStorage, err = app.Storage(ctx)
if err != nil {
    log.Fatalf("app.Firestore: %v", err)
}

Cloud Storageのクライアントも init() で初期化しておきます。

type User struct {
    ProfileImageUrl struct {
        StringValue string `json:"stringValue"`
    } `json:"profile_image_url"`
}

ドキュメントの構造体をこのように定義しました。profile_image_url というキーに画像がアップロードされたURLが保存されます。

func Resizing(ctx context.Context, e FirestoreEvent) error {
    url := e.Value.Fields.ProfileImageUrl.StringValue
    cli := http.Client{}
    resp, err := cli.Get(url)
    if err != nil {
        log.Fatal(err)
    }
    src, _, err:= image.Decode(resp.Body)
    if err != nil {
        log.Fatal(err)
    }

    size := 320
    img := imaging.Resize(src, size, 0, imaging.Lanczos)
    encoded := &bytes.Buffer{}
    jpeg.Encode(encoded, &*img, nil)

    bucket, err := fbStorage.Bucket(bucketName)
    if err != nil {
        log.Fatal(err)
    }
    path := fmt.Sprintf("resized_images/%dx.jpg", size)
    obj:= bucket.Object(path)
    writer := obj.NewWriter(ctx)
    io.Copy(writer, encoded)
    defer writer.Close()

    return nil
}

Function本体です。imaging(https://github.com/disintegration/imaging )を使い 320x にリサイズしてアップロードします(別途Storageへの追加をトリガーにしてUserドキュメントにパスをセットします)

検証してみたところNode.js版での公式ドキュメントでの解説にあるようなImageMagickのconvertコマンドを外部で実行するような方法*4と比べて、ファイルに書き出しがない部分がうまく効いて大量のリサイズが一度の実行でできそうでした(リサイズ処理の品質に差がでるかもしれないので別途評価が必要です)。

デプロイ速度やFunction起動速度について

Cloud Functionsのデプロイはローカルにあるソースコードを対象として、依存モジュールが記述されている package.jsongo.mod から自動的にクラウド環境でビルドが走るようです。

firebase-tools を使ったデプロイを行っていた時は、functions/ にあるすべてのファイルが対象になり1つのFunctionのリソースへアップロードされていました(GCPコンソールからアップロード済みファイルが取得できるので確認できます)。

またFirebaseユーザーの間でFunctionの高速化テクニックとして環境変数から探索して、Node.jsの依存モジュールの動的読み込み制御する方法が知られています。*5

これらの方法と比べてアーキテクチャ的に改善する可能性はあるなと思いつつも、まだ安定性やアーキテクチャの評価中なので詳しくは比較できていない状態です。

ビルド+デプロイツールの改善

firebase-toolsを使わくなることで、複数のFunctionの依存を管理するための方法や開発やデプロイを楽にする方法を別途用意しなければいけません。

Functionを分割して複数の依存を管理するパッケージができたことで、ソースコードはmonorepoの状態になります。そのためlerna(https://lerna.js.org/ )やBazel(https://www.bazel.build/ )のようなツールが機能する環境になるかもしれません。

ただfirebase-toolsを使った環境は並行して維持できるので、段階的に移行して検討するつもりです。

GCPサービスを使ったさらなるFirebaseアプリケーションの拡張

※Firebase & Google Cloud Platform*6

Cloud FunctionsはGoランタイムの他にはPythonランタイムもあり、そちらでも同様にトリガーFunctionが書けるので何か活用法があるかもしれません。

また各FirebaseやGCPサービスには対応したREST APIが公開されていることも多く、SDK対応言語以外でもクライアントを自作して拡張することができます。

もちろんCloud Functionsですべてを行うことにこだわらずとも、HTTPリクエストをトリガーとしたAPIサーバーのFunctionや、SSRを実行して動的HTMLを返す常に待ち受けしているFunctionは、同時処理数に優れたCloud Runに移すことができそうです。

またCloud Run同士で連携して(RESTやunary gRPC)サーバー間通信でシステムを拡張の目的でも利用できそうです。

他にはCloud SchedulerとCloud Tasks、Cloud Dataflowを使ったバッチ処理。Firebase AnalyticsとCloud FirestoreをBigQueryにエクスポートして分析し、その結果をシステムに反映させるなどを私たちも既に行っています。

まとめ

このようにFirebaseはGCPの既存の仕組みを使い易くラップしたものなので、必要に応じてGCPのリソースを活用して最適化することができます。

Firebase Advent Calendar 2019 - Qiita

明日の担当はVexus2さんです。お楽しみに。

参考文献

いい感じにiOSアプリ開発プロジェクトを立ち上げる方法を考える

f:id:laiso:20160718030202j:plain
pxhere.com*1

ここ数年、新規に開発するモバイルアプリのリードになる機会が何回かあり。プロジェクトの開始時期に毎度、README.md に開発方針を記述していたのだけど、いつも似たような内容になり公共性がありそうなのでそのままブログにした。

普段から「今回はアーキテクチャは何を採用しましょうか?」みたいな段階から議論がはじまるのを避けた方が建設的だと思っているので、その思想が反映されている。

想定する状況

我が社はこれからゼロからモバイルアプリを使ったサービスを提供するところであり、人手は少ないが開発初期段階から技術に投資する意気込みはあり、いずれはサービスの大ヒット、組織の大規模化を見据えて段階的に成長するアーキテクチャをバーンとやっていきたい。

事業領域は例えば決済サービスやビジネス系のモバイルアプリを想定している。

これは何?

iOSアプリのソフトウェア設計面についての現時点での考えを未来のメンバーに共有するための文書

基本方針

「ふつうのことを普通にやる」

ふつうとは—— ふつうの〇〇プログラミングというシリーズの良書があったり、Railsコミュニティなどでコンテキストとして利用されていたりする便利なイディオム。*2

僕の解釈では問題に対して多くの開発者が共通認識で選択する解決手法を採用すること。それによって環境要因や個人の嗜好によって偏りができるのを防ぐことができる。

なぜそうしたいのかというと、先の見えない開発なので技術的に凝ったことや挑戦に労力を使うよりプロダクトのUXに大きく寄与する部分に注力するため。技術的な挑戦がUXに直接的に関わる時は別。個人やチームの成長のための技術的投資は基本的にサイドプロジェクトでやることにしてる。

プロダクトの特性上、モバイルのアプリケーションの表面上でできる体験より。バックエンドのビジネスシステムの信頼性や運用にかかる効率化が価値となると考える。 (一方TikTokやインスタみたいな没入型の体験を提供するアプリケーションの開発に従事する人たちにはまた別の価値観があるのかもしれない)

決めたこと

  • React Nativeを使わない
    • 開発対象が素朴なUIを持つツールである。ドラゴンも出てこないしアイドルのライブ配信もない
    • ユーザーがKyashやメルカリみたいなものを期待するとギャップが生じガッカリさせてしまう
    • アップデート・保守面のコストが許容できない
    • PMF検証フェーズであり、クロスプラットフォーム提供の旨みがない。アーリーアダプタもほぼiOSユーザーと予想
  • RxSwift/MVVMでがんばらない
    • 「大いなる力には大いなる責任が伴う」 (意訳: 強い人は勝手にやってくれ)
    • 経験上FRPや宣言的View+データバイディングをがんばりはじめると、受け入れる複雑さがコードの保守性や品質向上にペイしないため
    • 非同期処理の問題解決のためだけに採用されるケースもある。将来のSwiftでasync/await世代の方法論が出てきた時のために、サードパーティ依存な部分を減らしたい
    • 小さく使う方法はあるのかもしれない。RxCocoaデータバインディングだけ抜き。とか。
  • Storyboardを利用。Feature単位でファイルを切る
    • 多人数開発ではないのでコンフリクトのリスクが低い
    • 標準コンポーネントを使って、デザイナ作業が不要である程度楽に開発が進む
  • 単体テストは書く
    • 状態管理フレームワークを検討するぐらい複雑なら必須!
    • TDDはがんばらずに、脳内でシュミレートして開発を促進させそうな部分だけ書く(自己判断)
    • CIサーバーの運用とかがんばらずに最初は手元で実行できればいい
    • 一方デプロイ自動化は今後何百回もやることがわかり切っているので最初からやる
    • モック化とかも考えずにE2Eになる部分がでてしまってもよい(むしろ設計に役立つ)
    • ドメイン/プレゼンテーション分離の設計を意識するため(あとからまとめてリファクタリングするのは難しい)

迷っていること

  • サーバーサイドとのデータやりとり
    • 受信のみ。送信のみ。双方向。リアルタイム性。などの性質を加味して検討してる
    • 巨大なHTTPライブラリ: Alamofireのようなものを採用するのか。範囲を絞って小さく使うのはありかもしれない
    • またはAPIKitの仕組み: ユーザーは少ないけど筋がいい
    • REST API: protobufやswagger-codegenみたいなものである程度自動生成するのか。実践したことないのでわからない
    • gRPC: サーバーがPaaSなど制限があると検討外になりそう
    • Firestore: 最近うまくいった(レイテンシ以外は) *3 APIクライアント層を書くかわりにサーバーエンジニアがCloud Functionsを書くことになりがち
    • AppSyncやApollo: GraphQLツール群次第

Firebaseを使った成長するモバイルアプリのための高速なプロダクト開発 / Rapid Mobile Application Development using #Firebase

「Firebaseを使った成長するモバイルアプリのための高速なプロダクト開発」というプレゼンテーションのスライドを公開します。

2018年にバンコクにきて以来取り組んでいたプロジェクトについて技術的な内容をトークにしました*1。以下が概要です

モバイルアプリケーションの開発プロジェクトはあらゆる意味で速度との闘いです。

複雑化するシステムアーキテキチャやプラットフォーム、開発ツールのエコシステム。数多くある選択肢の中から私達は2018年にFirebaseを使い、1つのモバイルアプリケーションを開発しました。

本トークではその時の経験を元に、プロダクト開発を高速化するために技術者視点でどのような貢献ができるのか? という知見をシステムアーキテクトの立場としてお伝えします。

開発中はFirebaseコミュニティの情報などが非常に助けになりました。この場をお借りして感謝の御礼を申し上げます。

*1:実はDroidKaigi 2019 応募用に書いていたやつ