最近の自分の働き方と思ったこと
家でリモートで働いていると移動時間とかも気にせず延々とコードを書き続けられるわけで、とはいえ仕事のコードばっかり書くわけにもいかないから趣味のコードも書く。
ただ仕事の進捗を遅延させるわけにはいかないので、必然的に仕事を終わらせてから1日の中の余った時間で趣味の方を書くことになる。
この感覚がなにかに似てるなあと思ったんだけど、昔ネトゲをやってた頃に似ているのかなあと。
10代の頃は毎日12-16時間くらいネトゲ(MMO)をやってたけど、そのゲームでは効率が良いと1時間80-100万経験値くらい稼げるので「毎日1,000万/経験値を稼ぐ」みたいなノルマを自分に課していた。 で、ノルマを達成したらレアアイテム狙いの狩場に移動してもいいしPvPやっても装備強化してもいい、みたいな。
特にオチもないんですが、「わかるわー」って人と語り合いたい気持ち。
Cloud Functionsトリガー実行時にFirestoreに認証情報を渡す
firestore.ruleで request.auth
を使って「レコードは自身の保持しているデータしか操作できない」ような制約を掛けるケースはよくあると思います。
match /profiles/{uid} { allow read, create, update if resource.data.userUid == request.auth.uid; }
普通に操作する分には問題ないんですが、例えばCloud Functionsの認証トリガーを利用して「ユーザが更新されたら、ユーザに紐づくプロフィール情報(Document)を更新したい」ようなケース。
exports.updateExternalProfile = functions.auth.user().onUpdate(event => { const user = event.data; const email = user.email; const displayName = user.displayName; db.collection('profiles').doc(user.email).update({ displayName }) });
そのまま更新すれば良いように思えますが、Cloud Functions内でのこの外部トリガーからの実行処理中はユーザログイン情報がないため、つまり request.auth
がないので更新しようとすると権限エラーになります。
Cloud Functions + Firestoreを使っていると苦しむこの問題。どうするか。
Issuesも上がっていますが、2018年03月時点ではまだfirebaseAdminでは解決していない模様。ちなみにRealtime Databaseは同様のことを満たすための databaseAuthVariableOverride
っていうオプションがあるみたいですね。
ではどうすれば良いか。 認証情報を冗長に持つ必要があるものの、firebase-admin-node に加えて firebase-js-sdk を使って、signInWithCustomTokenメソッドを使ってログイン状態にさせると一応動くようになります。
const admin = require('firebase-admin'); const serviceAccount = require('/path/to/serviceAccount.json'); admin.initializeApp({ credential : admin.credential.cert(serviceAccount), }); // 追加処理 const firebase = require('firebase'); require('firebase/auth'); require('firebase/firestore'); firebase.initializeApp({ // webAppの設定 }); const signIn = async (userUid) => { const customToken = await admin.auth().createCustomToken(userUid) return firebase.auth().signInWithCustomToken(customToken) } exports.updateExternalProfile = functions.auth.user().onUpdate(async event => { const user = event.data; const email = user.email; const displayName = user.displayName; // 追加処理 await signIn(email) db.collection('profiles').doc(user.email).update({ displayName }) });
こんな感じ。 認証周りの冗長感が否めないので、早くfirebaseAdmin側で対応してくれないかなー。
FirestoreのSubCollectionに対してQueryが使えない問題にどう立ち向かうか
Firestore、便利ですよねぇ。 ただ、2018/03執筆時点ではまだβなので荒削りだったり要件満たしにくい部分でつらいなぁっていう部分はいくつかあります。
その1つが表題の件の「SubCollectionにQueryが使えない」問題。 どういうことかというと、例えば「CDごとに曲名とその曲番号(連番)を保持する」構造を例に考えてみます。
- albums [collection] - title - musics [collection] - title - songNumber
Firestoreでどう表現するかというと、disksっていうCollectionを作って、その中にSubCollectionとしてmusicsを持つ形が一番シンプルに見えます。
こんなイメージ。ただ、この構造の場合は表題の制限があり、musics内のデータを直に参照したいケース、 具体的には「ユーザ属性は曲のタイトルを持っているが、その曲がどのアルバムに属しているか分からない(紐付けることができない)」時に、sounds内のデータを特定することができません。 この件についてStackOverFlowでGoogleエンジニアが「Collection group queryという機能で提供する予定だが、提供はすぐではない」という回答をしています。
ObjectやArrayとして下位データを持つ方法もなくはないですが、そもそものSubCollectionのメリットを活かせないのではここでは考慮から外しつつ、 Collectionであることメリットを活かし現時点で解決できるパターンをいくつか見ていきます。
親CollectionにSubCollectionのキー一覧を持つ方法
親のCollection側に、soundsのキー一覧を持つのが一案としてあります。
soundKeys: [ "1. 主よ,人の望みの喜びよ(In 4 Mix)" "2. 眠れる森~Sleeping Forest", "3. Mouring The Passing Time" ]
キー一覧を持つ際、上記のようにFirestore内のデータとして配列で持ちたくなります。 が、Queryでwhere in句のように配列を対象に検索することができないので、以下のようなObject形式で持つ必要があります。
soundKeys: { "1. 主よ,人の望みの喜びよ(In 4 Mix)": true, "2. 眠れる森~Sleeping Forest": true, "3. Mouring The Passing Time": true }
sound側のkeyを持っている状態からこのalbums -> soundsのdocを取得する場合には
const userPlayingTitle = '2. 眠れる森~Sleeping Forest' const querySnapshot = await db.collection('albums') .where(`albums.${userPlayingTitle}`, '==', true).get() ...
というような形でquerySnapshotを持ってくることができるようになりました。
Puppeteerで page.$(selector) で絞り込んだ要素から更に子要素指定を行う
最近がっつりとPuppeteerを触っている。 Puppeteer(書きづらい)とは、Headless ChromeをNode.jsから扱うためのライブラリ。
開発が速いのでググって出てくる情報は陳腐化していることがままあるので基本的には公式ドキュメントを読んでもらうのが大前提として、v1.1.1を触っている時点で得られた知見や小ネタちょこちょこと共有したい。
puppeteerの知見ですが、開発の流れが速いのでブログだったりの記事は古くなってるので公式のhttps://t.co/0agihTy0aCを読むのがベストっぽい。 ElementHandleからselector指定でもv0.13.0から取れるようになってる。
— べくさす (@Vexus2) 2018年2月23日
page.$(selector) で絞り込んだ要素から更にselector指定を行う
<div class="row"> <div class="col"> <div class="itemDetail">りんご</div> </div> <p class="price">120円</p> </div> <div class="row"> <div class="col"> <div class="itemDetail">みかん/袋</div> </div> <p class="price">520円</p> </div>
もともと page.$
page.$$
で絞り込んだ子要素 ElementHandle は .$
.$$
メソッドを持っていなかったので、page.$$evalで指定してループ内で取り出す・・・みたいな必要があったが、
v0.13.0 からは以下のように、取得したSelectorのオブジェクト(ElementHandle)から更に絞り込むような書き方ができるので前よりだいぶ直感的に書けるようになった。
const rows = await page.$$('.row') for (const row of rows) { const itemDiv = await row.$('div.col > div.itemDetail') console.log(await (await itemDiv.getProperty('textContent')).jsonValue()) const priceDiv = await row.$('p.price') console.log(await (await priceDiv.getProperty('textContent')).jsonValue()) }
Scalaコップ本メモ
そんなわけで最近Scalaを書き始めているわけですが、1ヶ月ほど前に今更ながらScalaスケーラブルプログラミング(通称コップ本)読んだので、その過程のメモ書きをまとめてみます。
ちなみに全部じゃなくて17章まで読みました。
実際書き始めてみると全然理解しきれてないところも多いのでもう一周読みなおそう・・・
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレスジャパン
- 発売日: 2011/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 12人 クリック: 235回
- この商品を含むブログ (46件) を見る
コップ本メモ
Scalaは、配列から式に至るまで、あらゆるものがメソッドを持つオブジェクトだとすることによって、概念を単純化している(P62)
「メソッドは副作用を持っていてはならない」というのは、関数型プログラミングの考え方の大きな特徴である。メソッドの作用は、計算して値を返すことだけでなければならない。このアプローチをとると、メソッドが入り組んだものにならないため、信頼性が上がり、再利用しやすくなる。(P63)
(関数型スタイルに慣れる)第一歩は、コードに現れる2つのスタイルの違いを理解することである。簡単な見分け方が1つある。コードにvarが含まれていたら、それはおそらく命令形のスタイルで書かれている。コードにvarが含まれていなければ、つまりvalだけが使われていれば、それはたぶん関数型のスタイルで書かれている。だから、関数型のスタイルに近づくための1角方法は、varを使わずにプログラムを書く努力をすることだ。(P72)
関数が副作用を持つかどうかを簡単に見分けるには、結果がたがUnitかどうかを見ればよい。Unitとなっていれば副作用がある。Unitという結果がたは関数が意味のある値を返さないことを意味する。(P73)
Scalaは、toStringやHashSetといったキャメルケースの識別子を使うというJavaの習慣に従っている。アンダースコアは識別子の中で使ってよいことになっているが、Javaとの一貫性を保つため、またScalaコードではアンダースコアが識別子以外の目的でよく使われているため、Scalaプログラムの識別子にはあまりアンダースコアを使わない。(P121)
Scalaでは定数という単語はvalを意味するわけではない。valは初期化された後は一定だが、それでも変数と呼ばれる。たとえば、メソッドのパラメーターはvalだが、メソッドが呼び出されるたびに、これらのvalは異なる値を保持して良い。(P121)
valを使うチャンスを探そう。valはコードを読みやすく、リファクタリングしやすいものにしてくれる。(P129)
一般に、varを避けるのと同じように、whileループも避けることをお勧めする。実際、whileループとvarはセットになっていることが多い。コードの中のwhileループには、疑いの目を向けてみるべきだ。whileやdo-whileを使う正当な理由が説明できないときは、これらを使わない方法を探してみよう (P131)
files <- filesHere
というような構文はジェネレーター(generator)と呼ばれ、filesHereからの要素に対する反復処理に使う (P132)- はじめて呼び方出てきた
breakやcontinueを使わずにプログラムを書く方法はいくらもある。そして、関数リテラルを利用すれば、元のコードよりも短いコードが書けることが多い。もっとも簡単な解決方法は、すべてのcontinueをifに、すべてのbreakをBoolean変数に置き換えることだ。 (P141)
Scalaのスコープ規則はJavaのものとほとんど同じだということが分かるだろう。JavaとScalaの違いは、Scalaでは入れ子になったスコープで同じ名前の変数を定義できることである。 (P143)
ScalaとJavaの違いとして注意すべきなのは、Javaでは、外側のスコープ変数と同じ名前を持つ変数を内側のスコープでは作れないことである。Scalaプログラムでは、外側の変数は内側のスコープでは見えなくなるので、内側の変数は同じ名前の外側の変数をシャドウイング(shadow)するということができる。(中略)通常は、外側の変数をシャドウイングするよりも、新しい意味のある変数名を選んだほうがよい (P145)
部分適用された関数とは、関数が必要とするすべての引数を渡していない関数呼び出し式である。必要な引数を一部または全く渡していないのである。部分適用関数式を作るにはメソッド名のうしろにアンダースコア書く。 (P157)
最後の処理として自分を呼び出す再帰関数を末尾再帰(tail recursion)と呼ぶ。Scalaコンパイラーは、末尾再帰を検知したら、パラメーターを新しい値に更新した後、再帰呼び出しを関数の冒頭にジャンプするコードに書き換える。解が末尾再帰になっていれば、実行時に余分なオーバーヘッドがかかったりはしない (P167)
パラメーターなしメソッドは、Scalaでは頻繁に見られるものだ。それとは対照的に、
def height(): Int
のように、空括弧付きで定義されているメソッドは、空括弧メソッドと呼ぶ。パラメーターがなく、ミュータブルな状態へのアクセスがオブジェクトのフィールドの読み出しだけなら、パラメーターなしメソッドを使うべきだとされている(特にミュータブルな状態を書き換えないことが大切)。この方法は、「属性をフィールドとメソッドのどちらで実装するかによってクライアントコードが影響を受けてはならない」という統一形式アクセスの原則に従っている。 (P184)原則として、Scalaの関数呼び出しでは、全ての空括弧を省略できる。しかし、呼び出されるメソッドがレシーバーオブジェクトのプロパティ以上のものを表す場合は、空括弧を書くほうが良いとされている。たとえば、メソッドがI/Oを実行したり、再代入できる変数(var)に書き込みを行ったり、ミュータブルなオブジェクトを直接または間接的に使って、レシーバーのフィールド以外のvarを読み出したりする場合には、空括弧を付けるようにすべきである。 (P185)
合成と継承は、既存の他クラスから新しいクラスを定義するための2種類の方法である。主としてコードの再利用を追求する場合には、一般に継承よりも合成を選んだほうがよい。継承は脆弱な基底クラス問題を抱えており、スーパークラスを書き換えると、サブクラスが利用できなくなることがある。 (P196)
配列の
++
操作は、2つの配列を連結する(P197)case修飾子が付けられたクラスをケースクラスと呼ぶ。この修飾子を使うと、Scalaコンパイラーはこのクラスの構文に適切な変更を加えるようになる。第1にコンパイラーはクラスと同じ名前のファクトリーメソッドを追加する。そのため、Varオブジェクトを作るのに、
new Var("x")
と書く代わりに、少し簡潔にVar("x")
と書くこともできる。第2にコンパイラーは、ケースクラスのパラメータリスト内の全てのパラメーターに暗黙のうちにvalプレフィックスをつける。そのため、パラメーターはフィールドとして管理される。第3に、コンパイラーがクラスにtoString, hashCode, equalsメソッドの「自然な」実装を追加してくれる。最後に、コンパイラーは変更を加えたcopyを作成するために、クラスにcopyメソッドを追加する。このメソッドは、1つか2つの属性が異なるだけでほぼ同じクラスのインスタンスを新たに作成する場合に便利だ。headやtailはともに一定の時間で実行されるが、initとlastはリスト全体をたどらないと結果値を計算できないので、リストの長さに比例する計算時間を要する。データ構造を設計するときには、リストの末尾ではなく、先頭にアクセスして操作できるようにするとよい。(P297)
写経しながらメモってたやつ
def approximage(guess: Double): Double = if (isGoodEnough(guess)) guess else approximage(improve(guess))
withPrintWriter( new File("data.txt"), writer => writer.println(new java.util.Date) )
↓
val file = new File("date.txt") withPrintWriter(file) { writer => writer.println(new java.util.Date)
株式会社nanapiを退職しました。
個人的な近況報告です。 7月下旬を持って株式会社nanapiを退職しました。
2013年1月に4人目のエンジニアとして入社したので約2年半ほど在籍してたことになります。
在職中は色んなところに手を出していて主に
- nanapi.jpの開発(Rails化、Docker化、モヤモヤnanapiとか)
- IGNITIONの開発
- 継続的インテグレーションの導入、運用
- CTO室な業務(エンジニア組織のこと考えたりとか採用活動とか)
- インターンシップの運営
・・・とか、大企業ではあまり経験できないようなスピード感の中いろんな業務を経験させて頂きました。今年からはリードエンジニアという立場でチームや組織を俯瞰して見ることが増えて、様々な面で成長出来たなーと実感出来る日々でした。
辞める理由は大きくネガティブな理由はなく、自分の技術的な興味の移り変わりが大きな理由です。優秀な同僚が多く、切磋琢磨し合える環境で働くことができたのはとても感謝しています。
エンジニアの採用サイトがリニューアルしていたので、せっかくなので貼っておきます。 辞めた身でこういうこと言うのも変な話ですが、nanapiはエンジニア以外の人のマネージャ・経営層も技術に対して理解があるので、新しいことにチャレンジし続けたい人にはとても働きやすい環境なんじゃないかなあと思います。
ちなみにあまり表に出てなかったですが、1年ごとに5万円までデバイス端末を購入補助される制度とか、入社時に2万円までPC周辺機器を自由に購入できる制度とかもあったりします。
次はフリークアウトで働きます。Scalaを書くのでPhpStormもRubyMineも使わなくなりますが、引き続きがんばります。(もちろんIntelliJ IDEAは使う予定)
Wishlist貼っておきます。