Runner in the High

技術のことをかくこころみ

J:COM船橋で2/17から通信障害があったらしい

J:COMから現時点で正式なアナウンスはなにもないが、どうやら千葉県船橋市内一帯で2/17から2/20にかけて通信障害が発生していたらしい。

この障害の影響で、上り速度が1Mbps以下しかでない日が3日間ほど続いた。症状としては上り回線の輻輳状態のような感じで、速度が出てもせいぜい0.3Mbps程度。下りには全く影響がなかった。

f:id:IzumiSy:20210221105438p:plain
スピードチェックのデータ。2/17をさかいにしてのぼりがグラフに出ないレベルの速度になっている。

しばらく様子を見ていたが、2/20の昼頃から改善していったことがグラフで分かる。

f:id:IzumiSy:20210221110757p:plain
2/20から2/21にかけての上り回線速度データ

1Mbps以下になってしまうと、普通にオンラインMTGで顔を出すことができなくなってしまうレベルなので非常に厳しい。

2/20ごろに電話で問い合わせたときには「こちらで確認できる障害は発生していない」とのことだったが、今日の電話でなぜか「障害が発生していたが復旧した」と伝えられた。しかし、現時点でもまだ障害情報が更新されていない。

Elmアプリケーションにおけるモジュールレベルでの詳細設計

Elmアプリケーションで比較的モジュール多めなアプリケーションの機能開発をするときに同僚とトライしている手法について。

言語的なElmのテクニックみたいな話ではなく、どちらかといえばもっと抽象的なハナシ。

1. 画面からざっくりとモジュールを見つけ出す

基本的に新しく機能を設計するときには画面設計みたいなものがデザインレベルで上がってきているはずなので、それを元に画面を構成するモジュールを分解する。この時点では画面ベースでやる。

自分がエンジニアHubで寄稿したElm記事でも、まずは画面をベースにして主要なモジュールを見つけ出している。

https://cdn-ak.f.st-hatena.com/images/fotolife/b/blog-media/20200302/20200302130323.png

2. モジュールを分類する

見つけ出したElmのモジュールを"TEAなモジュール""そうでないモジュール"で分類する。依存関係も見つける。

"TEAなモジュール"とはMsg型とupdate関数を持つTEA的なライフサイクルに乗るモジュール。"そうでないモジュール"はその名の通りそれ以外のもの。

TEAなモジュールを見つけ出すことはかなり大事で、ElmアプリケーションにおけるTEAなモジュールは必ず大元になるApp.elmだとかMain.elmみたいな上位のモジュールのupdate関数から自分のupdate関数に処理を移譲してもらう必要がある。なので、依存関係をあぶり出す段階でTEAモジュールの依存元になるモジュールにTEAなものが無ければ設計は破綻しており、この時点でモジュールの依存関係がミスっているということが分かる。

意外と見落としがちなのがPorts周り。update関数だけではなくsubscriptions関数も依存元モジュールにTEAなモジュールが必要であることに注意。また、Ports経由でデータの取得依頼とその結果の受け取り待ちをするような機能がある場合には、それらの処理のPortsどのようなポリシーの元でどのモジュール内に定義するかを考えておく。Ports定義はTEAなモジュールの中だけに限定するなど。

ElmにおいてPortsはかなり飛び道具的な機能であるため、ここのルールがガバると複数人開発ではすぐカオスになる危険性がある。

サードパーティー・モジュールの取り扱い

サードパーティーのモジュールを導入する際には移譲するためのラッパー・モジュールを用意する。

このモジュールはサードパーティー側のインタフェース設計によらずTEAなモジュールであることが望ましい。理由は、依存先のモジュールが変更されても最大公約数的に影響範囲を抑えられるようなインターフェイスを提供するため。詳しくは以下の記事で解説している。

izumisy.work

3. モジュールのインターフェイスを設計する

見つけ出したモジュールのインターフェイスをざっくりと書き出していく。

細かい実装までは書かない。

module Page exposing (Model, Msg, init, update, view)

-- model

type Model
    = Model

init : Model

-- update

type Msg
    = Msg

update : Msg -> Model -> ( Model, Cmd msg )

-- view

view : Model -> Html msg

上記はTEAなモジュールのインタフェース設計の例だが、ここでは外部のモジュールから依存される関数や型を意識する。外部のモジュールはどういう風にデータを取り出したいだろうか?というのを考えて関数を作っていく。これを考え始めると自ずとModelの設計が必要になるため必要に応じてModelも詳細化していく。なお、この時点ではモジュール内部でしか使わない非公開関数は書いても書かなくてもいい。

このインタフェース設計の時点でカプセル化したモジュールのインターフェイスを追求していこうとすると、ちらほら「結局exposingですべてのデータも関数も公開しなくちゃダメだ」みたいなモジュールが現れる。exposingで全公開 exposing(..) するようなモジュールは前提としてNG。これは、その実装をモジュールへ分割する意味がないモジュールの責務が薄いことを示している。そのようなモジュールは解体して他モジュールへ統合するか、依存元から責務を移動してみる*1。モジュールを作る理由がなければ作らなくてもいい。

モジュール設計に「機能による区分」を積極的に採用してしまっている場合にも、責務の薄さが表れることがある。モジュールの区分軸の話は過去記事を参照のこと。

izumisy.work

ここまで事前に設計段階で詰めておけば、残りは画面と具体のロジックを実装することに集中できる。また、作業の分担などを行ったり引き継ぎを行う際も、設計まで固められているので知識レベルを揃える工数を少なくできる。

あとはこれを関係者にレビューしてもらうなどして設計の妥当性を高めていく。

思うこと

Elmは他のフレームワークと比べて「できないこと」が多い分、設計の際になにをどこに置くべきか、みたいな観点が明確になる。設計段階でモジュール設計に時間を使って矛盾点をあぶり出すことで「この機能って本当にこの設計で作れるのか?」みたいな部分が事前に見つけ出される。これは、モジュールの設計がTEAによる具体的な制約を受けているからだ。制約の具体性と予測可能性(Predictability)は比例の関係であり、これはElmのTEAに限った話ではない*2

また、先回りしてモジュール設計をレビュワーやメンバと握っておくことで「このモジュールはなんのための存在か?」みたいな実装の大前提になるような知識を共有できる。地味にこの部分の齟齬がでかいとレビューで時間を食ったり、アプリケーション全体でモジュール設計の方針がめちゃくちゃになっていったりする。

小さい機能であればここまでやらなくてもいいが、新しく大きな機能開発でモジュールを作ったりする場合にはこれくらいやっておくと工期や品質の安定感が違う。

*1:とはいえ、単なる定数的な型とか関数のあつまりにしかならないようなモジュールが生まれることは現実世界ではあるっちゃあり、ここはベストエフォートでって感じ。こういうモジュールもちゃんと仕様を分析すれば適切なモジュールに統合して凝集性を高められたりするが、仕事でやるとなると工数的にやっていられない場合もある。

*2:たとえばクリーン・アーキテクチャもFluxアーキテクチャも、わざわざアプリケーション設計に制約を与えることでモジュールの構成や責務を予測しやすくしている。

2021年に思うこと

2020年内はいろいろ忙しくて振り返りとか書けなかったので、代わりに2021の今思っていることを。

ソフトウェア・エンジニアとしてのキャリアについて

去年はマネージャロールへの転向という自分の中での転換点の年であり、対外的な活動よりもいったん自分の会社に100%を注いでみようと決意した年でもあった。

12月にはひとつ大きな機能開発をチームでやりきり、チームのメンバや同じくマネージャロールの人々に支えられて「マネージャという仕事も悪くないな」と感じた。チームというのはひとりで成し遂げられないことをするためにある。 izumisy.work

技術的なキャッチアップに関してはかなり適当にやっていた2020年ではあったが、自分の中では「世の中の先端技術を追従しなければ」みたいなよくある焦りは全くと言っていいほどない。

技術に興味がなくなったというわけではないが、自分の中でソフトウェア・エンジニアという職業の捉え方が変わったのがひとつの理由ではないかと思う。自分の解釈では、ソフトウェア・エンジニアは研究職のようなごく少数の人々を除いてコードが書けるビジネスマンのような存在である。

「ビジネスマン」と表現しているのは、会社の利益のためであればコードを書く以外の業務もやるが、強みとしてコーディングがあるというだけの存在であるということを意味している。これは特に自分がベンチャー企業で働いているということから来ている解釈でもあると思う。企業規模が大きくなれば「効率性のための分業が云々」みたいな話が出てきて解釈は変わってくるだろう。

逆に、組織とソフトウェア・アーキテクチャの合理性みたいな部分には逆に興味が出てきている。 izumisy.work

プライベートについて

猫と一緒に生活をするようになった。

f:id:IzumiSy:20210101191109j:plain
寝る猫

人間と違って完全に生理的欲求だけに生きているので見ていておもしろい。

猫を見ていると、自然界の脅威からは解き放たれた代わりに労働という重荷を背負うことになった我々人間が、果たして猫と同じくらい幸せなのかが疑わしい。改めて人間としての生き方を問うてくる存在だと思う。

官僚制とマイクロサービス

現代では官僚制というとなんだか政治的でネガティブな面ばかりが取り沙汰されている面があるが、実際には業務の効率化という観点では、近代的な合理性を追求する優れたモデルである。

官僚制にはネガティブな面はあるものの、組織としてスケーラビリティを維持するために合理性を追求することで自然と官僚制に向かっていく。一度官僚制を取り入れたならば、もはやそれなしでは組織は成り立たなくなる。

マックス・ウェーバーによる官僚制の定義

官僚制を定義した社会学マックス・ウェーバーによる分類では、官僚制組織はざっくり言うと以下の性質を持つ。

  • 標準化された職務
  • 活動の独立(分業)
  • ピラミッド型の体系だった命令系統
  • 形式的かつ没人格的な客観的行為の要求
  • 文章を中心としたコミュニケーション形態

大前提として、すべての人間は「もの」としての規則に従い「もの」として扱われる。人間の非合理的な恣意性を排除し、平等と独立性を保証する。人に依存しない(属人性を下げる)ことで作業の品質を担保し、業務遂行効率のバラつきを軽減するのが官僚制の合理主義モデルである。

官僚制が存在する場所では労働者の交換可能性が高く、個々人の能力に依存しない。また、業務内容が標準化されており、属人化されないぶん生産性が保たれている。

身近で官僚制が最も顕著に見られる場所として工場やファミレス、行政機関などが上げられる。

官僚制としてのマイクロサービス

さて、マーティン・ファウラーの提唱するマイクロサービス・アーキテクチャウェーバーの官僚制に照らし合わせてみると、性質にいくつかの類似点が見られる。実際にいくつかの例を挙げてみる。

  • マイクロサービス間の通信はgRPCやJSONRPCなど、文章ではないものの形式化されたコミュニケーション形態へ統一されている。
  • マイクロサービスは単なるシステムとして扱われ、そこに人格的な主観性はない。マイクロサービスを操作する主体はマイクロサービスを「もの」として見ている。
  • マイクロサービスごとに与えられた職務が標準化されているECサイトであれば配送マイクロサービス、注文マイクロサービス、など)
  • マイクロサービスごとの業務は全て独立して行われる(設計、開発、デプロイ、テスト、など)

照らし合わせてみると、官僚制の考え方をソフトウェア・アーキテクチャに適用したものがマイクロサービスと言ってもさほど違和感はない。モノリシックで巨大化したソフトウェアをスケールさせるためのアーキテクチャがマイクロサービスであるという観点から見ても、スケーラブルな組織体制としての官僚制と目的は一致する。

マーティン・ファウラーはマイクロサービスを「変化に強いアーキテクチャ」であるとしているが、一口に「変化」と言っても実際には「組織的な変化」と「技術的な変化」があると考えられる。技術的な変化の観点では納得がいく(独立性、交換可能性、など)が、組織の点ではどうか。組織の形が変わるとき、システムはどこまでその影響を緩衝できるだろうか。

サム・ニューマンのマイクロサービス・アーキテクチャではマイクロサービスを適用すべきではないケースとして「ドメインへの理解が低い場合」を挙げているが、これは「ドメインに変化を与えたい場合」にも当てはまると言える。ドメインが頻繁に変化する組織... たとえばベンチャーやスタートアップであるが、そのような組織においてマイクロサービスはどう影響するだろうか。

いずれにしても、組織という観点ではマイクロサービスはIT企業における官僚制の適用例だと言える。

官僚制の逆機能

しかし、もちろん(というか当然?)官僚制にはよく知られた逆機能(欠点)がある。

官僚制の逆機能はR.K.マートンによって指摘されているが、その中で最もよく知られるものはセクショナリズムである。組織間で独立性が高まった結果、連携は文書コミュニケーションのみで行われ、組織全体の生産性を高めることよりも自分たちの職務を遂行することだけが目的化してしまう。

この辺の欠点は行政機関などの仕事ぶりを見ればよく分かると思われる。また、より卑近な例ではいわゆる大企業病と揶揄されるものがこのセクショナリズムそのものであると言える。勘違いしてはいけないのは、大企業だからセクショナリズムに陥るのではなく、セクショナリズムを伴ってでも属人性を排した合理的な業務を追求しなければ組織はスケールできない。

また、官僚制で問題が発生するのは標準化・ルール化できない特殊な業務が発生するときだ。いわゆる「お役所仕事」という言い方をされるような融通の聞かないあの感じである。融通の聞かない組織では技術革新もイノベーションも起きない。業務自体がフレームワーク化されてるため、業務改善なども非常にコストがかかる。このような理由から、組織の変化が組織外の変化に追従できなくなることも多い。

さいごに

究極的に言えば、官僚制のネガな部分だけを排除した形で組織をスケールさせつつ、マイクロサービスの技術的なよさを享受したい。これにつきる。

しかしながら、世の中のプレゼンスのある企業を見てみると利益追求のために必ず大きな組織になるし、その過程には業務の合理化が存在する。会社を存続させるためには利益を上げ続けないといけないし、そこに官僚制的な側面が現れてくる。

話をもう少し深掘ると、利益追求という観点の「合理化」には近代資本主義の存在がここに絡んでくる... が、書いていると記事が終わらなくなってしまうのでこの辺で。

参考資料

官僚制度とは何か - 組織論Ⅰ 立教大学経営学部

elm-firestoreでJSを1行も書かずにElmアプリケーションでFirestoreを使う

この記事はElm Advent Calendar 2020の8日目の記事です

今年の春頃からコツコツと個人開発のアプリケーションで使うためのElm用Firestoreライブラリのパッケージを作っていたので、その紹介をします。

github.com

できることはREADMEに書いてあるとおりで、基本的なCRUDオペレーションが一通りサポートされているのと、たぶんトランザクションもかけられます。

f:id:IzumiSy:20201209230333p:plain

また、このelm-firestoreは独自のエンコーダ/デコーダをパッケージのモジュールとして提供しており、Firestoreとの通信で使用される特別なJSONのデータ構造をパッケージの利用者が意識しなくてもよい作りになっています。

READMEにおいて "A type-safe Firestore integration for Elm." と謳っているのは、JSを書かずにFirestoreとの連携が実装でき、さらにエンコーダ/デコーダでも型安全性が担保されているという理由からです。

使い方

さて、実際のコードの雰囲気を見ていきます。

elm-firestoreでは取得されたデータは Firestore.Document というレコードで取り出せるので、それを格納できるようにしておきます。

また、Firestoreで利用可能な特別な型として ReferenceGeopoint がサポートされています。

import Firestore
import Firestore.Types.Geopoint as Geopoint
import Firestore.Types.Reference as Reference

-- model


type alias Model =
    { firestore : Firestore.Firestore
    , document : Maybe (Firestore.Document Document)
    }


type alias Document =
    { timestamp : Time.Posix
    , geopoint : Geopoint.Geopoint
    , reference : Reference.Reference
    , integer : Int
    , string : String
    , list : List String
    , map : Dict.Dict String String
    , boolean : Bool
    , nullable : Maybe String
    }

Firestore 型の生成は初期化は次のように行います。

もしもFirebase Authorizationを利用している場合には withAuthorization 関数を用いて認証トークンを与えることができます。

その他にも withDatabase 関数などでデフォルト意外のデータベースを指定したりすることもできますが、これらはoptionalです。

import Firestore
import Firestore.Config as Config

init: Firestore.Firestore
init =
    let
        config =
            Config.new
                { apiKey = "your-own-api-key"
                , project = "your-firestore-app"
                }
                |> Config.withDatabase "your-own-database" -- optional
                |> Config.withAuthorization "your-own-auth-token" -- optional
    in
    config
        |> Firestore.init
        |> Firestore.withCollection "documents" -- optional

ドキュメントの取得は非常にシンプルで、次のように書くだけです。

取得先のパスは withCollection 関数を使って事前にセットしておきます。

fetchDocument : Firestore.Firestore -> Cmd Msg
fetchDocument firestore =
    firestore
        |> Firestore.get decoder
        |> Task.attempt GotDocument -- GotDocument (Result Firestore.Error (Firestore.Document Document))

get 関数はデコーダを受け取りますが、このデコーダFirestore.Decode モジュールが提供する関数群で組み立てていきます。

エンコーダのほうも、同様にパッケージから提供されている Firestore.Encode を用いて組み立てていく形になります。

import Firestore
import Firestore.Decode as FSDecode

decoder : FSDecode.Decoder Document
decoder =
    FSDecode.document Document
        |> FSDecode.required "timestamp" FSDecode.timestamp
        |> FSDecode.required "geopoint" FSDecode.geopoint
        |> FSDecode.required "reference" FSDecode.reference
        |> FSDecode.required "integer" FSDecode.int
        |> FSDecode.required "string" FSDecode.string
        |> FSDecode.required "list" (FSDecode.list FSDecode.string)
        |> FSDecode.required "map" (FSDecode.dict FSDecode.string)
        |> FSDecode.required "boolean" FSDecode.bool
        |> FSDecode.required "nullable" (FSDecode.maybe FSDecode.string)

基本的に、Elmにおいては対象となるひとつのデータ構造に対してエンコーダとデコーダを実装することになりますが、それはelm-firestoreでも同じです。

しかし、この手間を省くことができるようにelm-firestoreでは Firestore.Codec としてCodecモジュールが導入されています。Codecを使うことでひとつの実装からエンコーダをデコーダを作ることができるので、基本的にはそれぞれ個別に定義するよりも、こちらを使うほうが楽ちんなのでおすすめです。

コーデックとはなんぞや?という方はこちらの記事をどうぞ。 izumisy.work

他にもドキュメントの作成、更新(patch, upsert)やトランザクションなど諸々ありますが、このあたりは応用的な話になるので説明を端折ります。詳しい使い方などはパッケージのドキュメントにサンプルコードとともに書かれていますので、ぜひ読んでみてください。 package.elm-lang.org

ある程度の機能は揃っていますが、まだまだ色んな機能の追加を画策中ですので将来的にダイナミックにメジャーバージョンで破壊的変更が入る可能性があります。ここだけ留意しておいてもらえれば幸いです。

リアルタイム・アップデートについて

こんだけ宣伝しといてアレですが、ひとつ決定的に惜しい点としてこのパッケージはFirestoreの一番の強みであろうリアルタイム・アップデートがサポートされていません。

これは内部的にHTTPのRESTful APIでFirestoreと通信をしているのが原因で、どうやら公式のJS製FirestoreライブラリとかはgRPCプロトコルを用いてFirestoreと通信をしているみたいです。詳細なことは僕には分かりませんがServer Streaming RPC的なのを使わないとドキュメントの更新はリッスンできないようです。少なくともRESTful APIでは100%できません。

というわけで普通にCRUDするには十分ですが、リアルタイム・アップデートを使うにはまだまだ、という感じです。いずれgRPCプロトコルでFirestoreと通信するように書き換えたいなと思っていますが、現状時間がなくてあまり手がつけられてません。

もし「やってやってもいいぜ」という方がいましたらぜひPRをお待ちしています
もちろん、バグ報告issueなども大歓迎です🙌

mercari/datastoreがDistinctOnメソッドをサポートした

先日PRを出していたものがマージされた。

github.com

GCP DatastoreのDistinctOnは履歴系テーブルを触るときに便利で、例えばチームごとのログイン履歴を保持するLoginHistoryというカインドに対して「全チームの最新のログイン履歴を取得する」というクエリがGQLで言うと以下のように一発で書ける。

select distinct on (teamId) createdAt, teamId 
  from LoginHistory 
  order by teamId asc, createdAt desc

Distinctだけじゃできないのん、と思われるがドキュメントによるとonする対象とprojectionしたいカラムが異なる場合にはやはりDistinctOnが使えないと上記のクエリと同じ結果は出せない。

SELECT DISTINCT a, b, c is identical to SELECT DISTINCT ON (a, b, c) a, b, c

もちろん、DistinctOnがなくてもアプリケーションサイドでgroup-byするような実装を書けばいいが、クエリで出せるならクエリで出したい気持ちがある。

イベント・ソーシングするようなデータ構造を持っている場合には便利に使えると思われる。

ElmでCodecを用いてエンコーダ・デコーダの実装をケチる

この記事はFringe81 Advent Calendar 1日目の記事です。

Elmでアプリケーション開発をしているとほぼ100%出現するのがエンコーダ・デコーダの実装。

基本的に両方セットで実装することが多いが、データ構造が共通であればCodecと呼ばれる類の機能を提供するライブラリを用いてエンコーダ・デコーダの実装をケチることができる。

Codecとは

Codecとは簡単に言うとデータ構造が共通なものであればひとつCodec用の関数を定義しておくだけで、エンコーダとデコーダを同時に実装することができる便利なもの。

まず、Elmでは書き込みと読み込みでエンコーダとデコーダが別れているため、基本的に同じデータ構造に対してそれぞれ実装する工数があるかつ実装が別々であるため改修時に実装ミスが起きるなどの可能性がある。 しかしCodecを利用することでエンコーダとデコーダをそれぞれ実装する必要がなくなる上、デコーダやエンコーダにだけtypoがあった!みたいなバグを減らすことができる。

Codec的なパッケージとしていま利用できるものにはminiBill/elm-codecMartinSStewart/elm-serializeのふたつがある。この記事ではそれぞれの違いや使い分けたほうがよさそうなポイントを紹介する。

elm-codec

package.elm-lang.org

最も最初にCodecという概念を誕生させたのはこのライブラリ。

兎にも角にもまずはサンプルコードから。以下のようなレコードに対してCodecを実装してみる。

type Language
    = Japanese
    | English
    | Other String


type alias Model =
    { name : String
    , age : Int
    , lang : Language
    }

Codecの実装はこんな感じになる。

-- codecs


modelCodec : Codec.Codec Model
modelCodec =
    Codec.object Model
        |> Codec.field "name" .name Codec.string
        |> Codec.field "age" .age Codec.int
        |> Codec.field "lang" .lang languageCodec
        |> Codec.buildObject


languageCodec : Codec.Codec Language
languageCodec =
    Codec.custom
        (\jp en other value ->
            case value of
                Japanese ->
                    jp

                English ->
                    en

                Other v ->
                    other v
        )
        |> Codec.variant0 "japanese" Japanese
        |> Codec.variant0 "english" English
        |> Codec.variant1 "other" Other Codec.string
        |> Codec.buildCustom

上記のようにまずレコードに対するCodecを実装した上で、そこに含まれるカスタム型のCodecも実装できる。

基本的に入れ子構造にしてデータ構造を用意でき、使っている感じはデコーダの実装に近いと思われる。パイプラインでバシバシつなげるのもいい感じ。

あとは用意されている関数を用いてValue型にエンコード・デコードできる。使い方は基本これだけ。

-- encode
Codec.encodeValue modelCodec model

-- decode
Codec.decodeValue modelCodec value

ドキュメントを見てもらえればわかるがstring, bool, intなどの基本的な関数は用意されており、ResultやDict, Setなどにも対応している。また、カスタム型も最大8バリアントまで実装されている。

エンコードされるJSONの構造

さて、ここまでくると一体どういうデータ構造のJSONエンコードされるのかが気になってくる。データ構造上はエンコーダとデコーダが共通でも動くものになるはずだ。

というわけで、上記のコードで示されているModelレコードをエンコードすると以下のようなJSONが作成される。

{
   "lang":{
      "tag":"other",
      "args":[
         "unknown"
      ]
   },
   "age":10,
   "name":"seiya"
}

agename は想像通りだが、カスタム型として定義されている lang には若干クセがある。

tag部分にはelm-codecが提供する Codec.variantN 関数に渡されているフィールド名が入り、そしてもしそのバリアントがタグとなるデータを持っている場合にはそれを args というフィールドで保持するという作りになっている。たしかに、こうでもしないとカスタム型はそのままJSONに格納できない。

elm-codecでは上記のようなデータ構造のみがカスタム型に対してマッピングできる作りになっているため、結論JSONのデータ構造がelm-codecによって制限されることになる。エンコーダ・デコーダを個別に定義しなくてよくなるのは楽ではあるが、JSONのデータ構造の柔軟性を失うのは若干痛い。

elm-serialize

package.elm-lang.org

elm-serializeはelm-codecよりも後発のパッケージであり、elm-codecと比較してパフォーマンスや安全性に関する点で違いがある。

パッケージ自体の使い心地はelm-codecとさほど変わらない。以下が実装例となる。Modelレコードはelm-codecの例で実装されているものと同じものとする。

-- serializer


modelSerializer : Serialize.Codec a Model
modelSerializer =
    Serialize.record Model
        |> Serialize.field .name Serialize.string
        |> Serialize.field .age Serialize.int
        |> Serialize.field .lang languageSerializer
        |> Serialize.finishRecord


languageSerializer : Serialize.Codec a Language
languageSerializer =
    Serialize.customType
        (\jp en other value ->
            case value of
                Japanese ->
                    jp

                English ->
                    en

                Other v ->
                    other v
        )
        |> Serialize.variant0 Japanese
        |> Serialize.variant0 English
        |> Serialize.variant1 Other Serialize.string
        |> Serialize.finishCustomType

定義した雰囲気もほぼほぼ同じだが、まずひとつelm-codecとの違いとしてelm-serializeはValue型だけではなくelm/bytesへの変換もサポートされている。対象となるデータ構造のサイズに応じてValue型とBytes型を使い分けることができる。

エンコードされるJSONの構造

最も異なるのは生成されるJSONの構造で、こちらはelm-codecよりももっとクセがある。

エンコードすると以下のようなデータになる。

[
   1,
   [
      "seiya",
      10,
      [
         2,
         "unknown"
      ]
   ]
]

elm-serializeはREADMEで「human-readableなフォーマットを求める場合には適さない」と書かれており、生成されるJSONの可読性を犠牲にしてJSONのサイズが小さくなるように工夫されており、そのためこのような形のデータ構造となっている。

また、elm-serializeはパッケージ内で定義されるエンコード・バージョンを生成されるJSONのデータに埋め込んでいる。上記のJSONで言うと配列トップレベルの 1 という数値がそれにあたる。この値はelm-serializeの中で定義されているものになっている。

{-| Possible errors that can occur when decoding.
  - `CustomError` - An error caused by `andThen` returning an Err value.
  - `DataCorrupted` - This most likely will occur if you make breaking changes to your codec and try to decode old data\*. Have a look at `How do I change my codecs and still be able to decode old data?` in the readme for how to avoid introducing breaking changes.
  - `SerializerOutOfDate` - When encoding, this package will include a version number. This makes it possible for me to make improvements to how data gets encoded without introducing breaking changes to your codecs. This error then, says that you're trying to decode data encoded with a newer version of elm-serialize.
\*It's possible for corrupted data to still succeed in decoding (but with nonsense Elm values).
This is because internally we're just encoding Elm values and not storing any kind of structural information.
So if you encoded an Int and then a Float, and then tried decoding it as a Float and then an Int, there's no way for the decoder to know it read the data in the wrong order.
-}
type Error e
    = CustomError e
    | DataCorrupted
    | SerializerOutOfDate


version : Int
version =
    1

https://github.com/MartinSStewart/elm-serialize/blob/1.2.1/src/Serialize.elm#L93-L112

JSONエンコード・バージョンを付与することで、仮に今後elm-serialize自体にbreaking changesがありエンコードされるJSONのデータ構造が変わってデコードエラーが発生したとしても、それがelm-serializeのバージョンアップ起因なのかそれとも純粋にデコード対象のJSONのデータ構造が間違っているのかを型レベルで判別できるようになっている。

このような後方互換性の考慮はelm-codecにはないため、この点はelm-serializeのほうが優れていると言える。

まとめ

エンコードデコーダをそれぞれ定義しなくていいなんて最高だな」と思って触っては見たものの、どちらもある程度のクセはあるため、どういう用途でCodecを使うかによっておそらく使い分けが必要になると思われる。

生成されるJSONのデータ構造をElmアプリケーションの外で触るようであればデータの構造は非常に大事であるのでelm-codecを使うべきだが、LocalStorageに保存するデータなどデータ・サイズに制限があるような環境においてはelm-serializeのほうが適していると言える。

ちなみにだがelm-firestoreにもCodecが実装されていて、データベースやLocalStorageに対するCRUDのようなデータソースの構造が一意に決まるシステムの入出力部分にはCodecのような仕組みを利用するのが適しているということが分かる。このような観点では、常に一意に入出力のデータ構造が決まるとは言えないサーバーサイドとの通信部分でエンコーダ・デコーダが必要になるWebフロントエンドのアプリケーションにおいてはCodecはあまり登場することはないのかもしれない。

ITエンジニアの副業あれこれ

いろいろやってみた知見が溜まったのでまとめ。

開発

時間給で労働を売る古き良きスタイルの副業。副業として機能開発とかバグ修正とかやったりする。

自分はUIがいい感じだったからという理由でOffersを使って副業を探した。

offers.jp

注意点として、日中まともに会社員やっている人だと、稼働時間が自ずと平日夜か土日だけになってしまう。これが厳しいところとして、副業先のチームの開発プロセスには載ることが基本できないため、非同期作業みたいな形になる&開発スピードが求められるコアな機能開発みたいなアイテムには入りづらくなる。

あと純粋にモチベーション的に平日夜とか仕事したくないときもある。でも副業でアサインされてると仕事しないといけない。場合によってはチームの開発作業の進捗妨害をすることにもなったりする。

独立して作業可能なアイテムを貰えるプロジェクトか、例外的に日中もコミュニケーションできる人の場合にはそこそこワークする副業だと思われる。しかし、ひとりで作業を進められない系の人(未経験とか)は確実にやらないほうがいいと思う。

メンタリング

自分はMENTAを使った。

menta.work

注意点として、MENTAはとんでもない低価格でメンターリングプランを提供するユーザーによって価格崩壊が起こっているので、ちゃんと金を稼ぎたければある程度の価格帯(30k-50k)でのメンタリングプランを提供する必要がある。もちろん高ければ高いほどコンバージョンしにくいが、ちゃんとした価格でまともな(?)メンタリングを受けたい層もマイノリティーではあるが存在しているので、そこを取りに行くのがよい。

私見だが、メンタリングにちゃんと金を払える人間のほうがコーチャブル*1でメンタリングに素直だと思うし、安くすることには安くする以上のリスクもある。

執筆(Webメディア)

エンジニアHubみたいなメディアで執筆をする仕事。

izumisy.work

基本的に1記事いくら、みたいな単価計算になる。すごく儲かるということはない。

最も重要な点としてメディアで執筆をお願いされるような記事というのは「自分が得意な分野」が対象になるということ。

自分が得意な分野ということは、記事を書くことは単なるアウトプットであって、自分の勉強になるかというと... そんなことはない。つまり、知っていることを書いているだけなので学びはほとんどない(編集の人が校正してくれるので文章を書く練習にはなるかもしれないが)。

技術書籍を執筆している人たちも同じようなことを言う。自分が知っていることをただ書くだけなので、自分の学びにはならない。それが許容できるなら執筆の仕事をやってみるといい。どちらかと言えば、金をかせぐというよりも自分のブランディングのため、というほうがモチベーションの向き筋としては正しいかもしれない。

技術顧問業(アドバイザー)

Twitterだとか知り合いのツテをたどって、自分の得意な領域でアドバイザー的なやつをやる。

自分はブロックチェーン技術に関する会社でElmのアドバイザーをやらせてもらっていた。

アドバイザー的な立ち位置であるからには「〇〇なときはどうするべきか」「この言語ではどうすることが最善か」などを知識や経験で持っていることが求められる。自分の場合は現職でElmをかなりの規模で使っているため、そのような知見を広められたらいいなという気持ちでやっていた。

また、顧問業は純粋にコードを書くのとはバリューの出し方が異なる。

開発のゴールはプロダクトを作ることだが、アドバイザーは実際に自分の手を動かすことは少ない。逆に、より大局的で大きなインパクトを生むことが期待される。例えばレビュー方針や設計方針など、チーム全体の生産性を向上させるためにはどうするべきか?を知識や経験を元に考えたりアドバイスしたりするなど。

コードを書いて機能開発をしたりバグ修正をしたりするというのは比較的わかりやすい価値提供であるが、顧問業となると単価もそれ相応に高くかつ設計やチーム全体のイシュー解決がメインの業務になるため、事業ドメイン理解も求められる。設計なんかはドメイン理解があって初めて正しさが分かるもので、ただ「デザインパタンやらフレームワークや言語が得意/詳しい」というだけで根本的な解決ができるかというとかなり怪しい。

自分が顧問業をやらせて頂いていたプロダクトは本業とは全く異なるドメイン領域のものだったため、この部分の理解が浅いことで設計のアドバイスが難しく感じることもあった。顧問業をやるにあたってドメイン理解は技術に関する知識以上に武器になる。

「技術顧問とはどうあるべきか?」みたいな話は元メルカリCTOのsotarokさんの記事もほぼ必読だと思う。

note.com

ブログ

自分の場合にはAmazonアフィリエイトリンクを記事中に貼っている。

例えば以下みたいな記事。

izumisy.work

エンジニアブログはうまく記事を書けば、はてぶでバズったりだとかTwitterで拡散されたりするので、その瞬間風速で金を稼ぐ。やり過ぎると「このブログはアフィ目的のやつだ」と気づかれる。自分も会社の後輩にこのブログがアフィ目的なやつだと思われている(実際そうだが)。

一番ラクだが、上で紹介した副業の中でダントツで一番金にならない。せいぜい毎月500円くらい。一度だけ例外的にはてブでバズってアクセスが跳ねたときは6000円くらい稼いだ。基本的に手間がかからないことが魅力。逆に言えばそれだけ。

なお、はてなブログはProプランになってもGoogleアドセンスの審査が通らないのでやれていない。これは、はてなブログ側の問題とのことで結局Pro契約の2万円をドブに捨てることになった。はてなさんどうにかしてくださいマジで。 → 今は審査に通るようになりました🙏

副業するべきか、どの副業がいいのか

個人的な体感として、一番簡単で金にならないのがブログ、一番難しくて金になるのが技術顧問業という印象。そういう意味では、やはり副業で開発かメンタリングをやるのが無難なのかもしれない。

会社にもよるが、本業でちゃんと給料もらえているor上がる可能性があるのであれば副業なんてする必要がない。むしろ本業でバリューを発揮して給与上げるほうが長期的には得なことが多い。副業には稼ぎすぎると確定申告とかしないといけなかったり、コンテキストスイッチなどの面倒臭さがある。副業で本業へのやる気が削がれるのは最悪だと思う。

一時期は不動産投資とかも考えていたこともあったが、知れば知るほど小さな会社経営並の難易度だと感じてやめた。サブリース業者に任せたりするのも、なんだか不動産業者という情報強者の言いなりになってしまう不安がある。

izumisy.work