生涯未熟

生涯未熟

プログラミングをちょこちょこと。

初心者Elmerがクソアプリを作った時に気付いたこと

この記事は Elm Advent Calendar 2018 の17日目の記事です。

今回は初心者Elmerが別のアドベントカレンダーでアプリケーションを作ってみた時に気付いたことをつらつらと書いてみます。

syossan.hateblo.jp

github.com

書き始め

以前、Elmに入門してからだいぶ時間が経っていたので、振り返りも兼ねて以下の神資料を読んで記憶を呼び覚ましました。

gitpitch.com

gitpitch.com

こうやって忘れても、ちょっと学ぶだけですぐに使い始められるのがElmの良いところですね。

さて、使い方を思い出したところで、今回の実装で自分にとって課題になりそうなところをリストアップしました。

まだ簡単なTODOリストを作ってみたレベルだったので、この辺りの実装に手こずるかなと。

APIサーバとの疎通

何はともあれコードをば。

大まかな流れとして

  1. Decoderの作成
  2. Decoderを使い、GETリクエストの作成
  3. GETリクエストを送信

ってな感じですね。

実装自体は参考になる記事があったりして、そこまで難しくはなかったのですが、Decoderで少し引っかかるところがありました。
本来的にはDecoderは以下のように作成します。

type alias Job = { name : String, id : Int, completed : Bool }

point : Decoder Job
point =
  map3 Job
    (field "name" string)
    (field "id" int)
    (field "completed" bool)

ただ、この map3 というのがマッピングするRecordのフィールド数に合わせて可変する感じで、例えば5つのフィールドがある場合 map5 としなくてはいけなかったりして、直感的に「なんか微妙だなー」と感じました。
そんな気持ちをTwitterで吐露したところ、Elm教の教祖様から福音がありました。

この json-decode-pipeline ってのを使えば、良い感じにpipelineで表現出来るのか、と試したところ良い感じだったので今回の形になりました。

Elmで分からないところがあれば雑にTwitterに書いとくと誰か答えてくれるかも?

あとは、URLにクエリパラメータを付けなくてはいけなかったのですが、今回は無理くりリクエスト作成時の処理に紛れ込ませました。
時間無かったので結構雑にやったのですが、ここ上手いこと出来る方法を知っている方がいれば情報くださいまし(ぉ

ページャーの実装

次にページャーの実装ですが、 elm-paginate を使いました。
今回では以下のように使用しています。

gist.github.com

大体の処理が PaginatedList を引数にとった Paginate.XXX で完結してますね。簡単。
ただし、ページャーで良くある「○○以降のページは...で表示する」とかは自力で実装する必要があります。難しくないけども。

その他諸々

その他ちっちゃいことなんですが、今回CSSフレームワークのBulmaを使ったんですが、以前TODOアプリを作成した時には elm-bulma を使用してElementを書いてくスタイルがあまり好きではなくて、 bulma.min.css を直接呼ぶ形を採用しました。

あと、やっぱりまだまだElmの日本語記事が少なくて「皆書いてくれ〜〜〜」って思ったりしました。
それでも公式ドキュメントとかちゃんと読めば済む話ではあるんですが、ライブラリとかたまにドキュメントが不親切な場合があるので・・・😇

まとめ

Elmは改めて良い言語だなーと感じましたね。
昔ちらっとReact+Reduxとかでフロント書いたりしましたが、今やれと言われても無理です。
しかし、Elmは学習コストがドチャクソ低いので、少々忘れたとしてもElm Guideをササッとなぞればすぐ書けるのが良き。
そういうところもあって、「普段フロントをあまり書かないサーバサイドの人間」に優しい言語でもあるのかな、と。

あとはコンパイラが優しいですね。「ここって間違えてるんだけど、もしかしてこうじゃない?」って教えてくれるので、「それや〜〜〜〜〜😇」って感じで解決して書けていけるので最高。 バーっと書き出して、実行した時にエラーがドンッと出るのが一番イライラしますからね。精神衛生上良い。

また、ライブラリの充実度がちょっと心配してたんですが、普通に使いたいものは揃ってる感じでした。
ただ、そこまで豊富にあるわけではないので、「このライブラリ使い心地がアレだから別の使お」ってなった時に類似ライブラリがあまり無い感じではありました。

という感じで、初心者が実際にちゃんとしたアプリケーションをElmで作ってみた感想をつらつらと書いてみました。

皆、Elmやろう!!!

ウルヴァリンがハッキングする映画はいいぞ

というわけで、こちらは プログラマーにおすすめしたい映画 Advent Calendar 2018 14日目の記事になります。

君は『ソードフィッシュ』を知っているか?

名前からして何の映画か分からない『ソードフィッシュ

まずはパッケージ画像を見てみましょう。

f:id:syossan:20181212011445j:plain

キャストが濃い!!!

特にジョン・トラボルタなんて濃さの塊ですよね。
パルプ・フィクションでもそうなんですが、トラボルタがいるだけで映画の雰囲気が3割増しで濃くなります。

さて、そんな中一番右にございまするお方は・・・

ウルヴァリンウルヴァリンじゃないか!

はい、というわけでヒュー・ジャックマンが出演しております。
いやー・・・かっこいい・・・雄って感じがして最高ですよね、ね?

ハル・ベリードン・チードルもMarvel映画好きからするとお馴染みの面々ですね。

肝心の映画自体の話に入るのですが、あらすじを簡単に説明すると

『凄腕ハッカーヒュー・ジャックマンジョン・トラボルタハル・ベリーが持ちかけた”ソードフィッシュ計画”なるハッキングでの裏金奪取計画に巻き込まれていく・・・』

という感じです。

大袈裟だけどそそられるプログラミングシーン

あらすじからも分かる通り、ヒュー・ジャックマンが凄腕ハッカーという設定なので何回かハッキングシーンが出てきます。
もう、それが「絶対にそんなん無理やろwww」ってツッコミたくなることうけあいなんですが、どこかそそられてしまう代物。

最初のハッキングは、ジョン・トラボルタがハッキングの腕を試すために並のハッカーが60分かかるセキュリティを60秒で突破しろというもの。
いやいやいやいや、無理無理無理無理!エンジニアの生産性が人によっては100倍差が出るとか言われるけど無理でしょ!

しかも、映画を観たらわかるんですが下半身をゴニョゴニョされながらハッキングさせられるんですよね・・・うらやましい

ただ、そこはウルヴァリン。鍛えた上腕二頭筋でバッチバチにキーボード叩きながらハッキングを成功させてしまいます。すげぇな、おい。

f:id:syossan:20181212013738p:plain
せやな

次のハッキングシーンは、ソードフィッシュ計画に使うワームを作るんですが、そのシーンがまた見物でめちゃくちゃ四苦八苦しながら謎の3DCGと向き合いながらプログラミングしてるんですよね。
荒唐無稽っちゃ荒唐無稽なんですが、ヒュー・ジャックマンが必死こいて頑張ってるのみてると、なんとも言えない「オッ、やっとるな」って気持ちになります。いいですね。


飲酒プログラミングでテンションの上がるヒュー・ジャックマン

正直なところ

ハッキングって要素は添え物で、本番は後半の圧巻な銃撃戦に詰まってます。
後半観てると「一体今までのハッキング云々は何だったんだろう・・・🤔」って感じになることでしょう。
あと無駄にハル・ベリーがエロい

まぁ、そんな感じで普通の映画として観るとまぁまぁな本作ですがエンジニアが観るとハッキングシーンにオッとなるのでオススメです。
Amazon Prime Videoにもあるので、気になった方は観てみましょう!💪

チャレンジングなことをしてみたらエッチなクソアプリが誕生した

どうも、この記事は クソアプリアドベントカレンダー の10日目のようです。

さて、今年ひり出したクソアプリは

エッチなやつです

f:id:syossan:20181210005011p:plain

エッチなリンク

業務で触った技術がなんか活かせないかな〜?と考えていたら、何故かエロクソアプリが誕生していました。不思議ですね。

どんなクソなの?

で、どんなアプリなのかというと「サクッと条件に合うAV女優探してサクッとxvideosかDMMに飛べるやつ」です。便利ですね。

これ以上、特に解説することがなくて困ってしまいましたので、技術的な解説に入りましょう。

技術の話

今回は、フロントをElmで、バックをGo + Lambda + S3 Selectで構築しました。

f:id:syossan:20181210011649p:plain

フロントの話

Elmを採用した理由としては、最近Elmを布教する教祖の人に影響を受けてTODOアプリ以外のもの作ってみてーなーということからやってみました。
実際ガッツリやってみると、コアなところはミニマルで言語仕様をサクッと把握出来るので学習コストがかなり低い言語だなーと改めて感じました。

ただ、パッケージによっては結構扱い方がピーキーなものしかなかったりして、まだそこらへんのエコシステム周りが潤ってない感はありました。
ハマりどころはあるものの良い言語です。皆さんもやりましょう。

あと、CSSフレームワークはBulmaを使いました。名前からして今回のアプリにぴったりですね。

S3のWeb Hostingを使ってるので、デプロイも npm run build して生成されたdist fileを放り込むだけで完了。平易。

さて、今回フロント側ではスケベデータ取得用Lambdaに問い合わせた結果を単純に表示しているだけの簡単な作りになっています。
あまり語ることが無いのでバックエンドの話に移りましょう。

フロントのソースコードはこちら

github.com

 バックエンドの話

バックエンドは手に馴染んだGoを使い、Lambdaに乗せてS3 Selectを用いてCSVデータを取得してくるように作りました。
S3 Selectが前から気になってて、やっとこさまともに使ってみたのですがドキュメントに書いてないハマりどころがあり、めちゃくちゃハマってしまいました・・・

その辺の詳しい話はGoアドベントカレンダーの記事で書こうかなと思うのですが、概要だけ書くと

  • メッセージタイプがRecordsの場合、65000バイトのchunk dataで送信される
  • CSV列の途中に欠損データがある場合、欠損データ行までしか比較演算子の対象とならない

という感じで、特に2つ目は1日溶かすレベルで悩みました・・・

そんなS3 Selectですが、1600件ほどのデータから簡単なクエリで引っ張ってくるだけなら300msくらいで返してくれるので、簡単なデータを扱うならこれでもいいのかなーと思える感じでした。

また、コンソールからSQLも叩けるのでデバッグとかに重宝しました。

S3バケット内のCSVにS3 Selectというタブがあるので

f:id:syossan:20181210015244p:plain

そこからSQL実行できます。

f:id:syossan:20181210015333p:plain

バックエンドのソースコードはこちら

github.com

まとめ

作った期間は2日くらいですが、すごく久々にクソをひり出せて満足感が高かったです。
用意したデータ自体の不備が結構あったので、全部で約1600件のデータしか返せないのが残念ですが、暇を見つけて充足させていきたいと思います。

みんな、エロはいいぞ。

現場からは以上です。

GoLandにCassandra対応が来たぞ!!

掲題の通り、GoLand 2018.3でCassandraのDataSourceが使えるようになりましたぞ!

www.jetbrains.com

今までCassandraのGUIツールといえば、DataStax DevCenterくらいしか無かったのですが、これで新しい選択肢が増えましたね。

qiita.com

試しに使ってみたのですが、他のDataSourceと同じように特に違和感なく使うことができます。いやー、嬉しい。

f:id:syossan:20181123183159p:plain

あと、DataGripはEAPになっている2018.3対応でCassandra Supportが来るみたいですね。
Release Noteにも書いてあるように「Thanks to our wonderful colleagues on the DataGrip team」だ!ありがとう!

guregu/nullでぬるぬるする

Goでサーバーサイドの開発を行っているとMySQLなどのRDBに接続して、取得した値をJSONで返す機会は多いはず。
そして大概ハマるのが「NULLを許容してJSONで返すのはどうやればいいんだ・・・」というところでしょう。

今回はそんな話をします。

ノーガード戦法

まずは特に何もしなかったパターンを見てみましょう。

テストデータは以下のようなものを使います。

+-------+------+
| name  | age  |
+-------+------+
| tarou |   12 |
| NULL  | NULL |
|       |    0 |
+-------+------+

適当な値が入ったレコードと、NULLが入ったレコードと、空文字・0が入ったレコードになります。
これを以下のようなコードで使ってみましょう。

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

type User struct {
    Name string
    Age int
}

func main() {
    db, err := sql.Open("mysql", "root:@/test")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT * FROM user")
    if err != nil {
        return
    }


    for rows.Next() {
        var user User
        rows.Scan(&user.Name, &user.Age)
        JSON, err := json.Marshal(user)
        if err != nil {
            continue
        }
        fmt.Println(string(JSON))
    }
}

これを動かしてみると・・・

{"Name":"tarou","Age":12}
{"Name":"","Age":0}
{"Name":"","Age":0}

おっ、NULLが空文字と0になりましたねー。
このように特に何もしなかった場合は型に応じた初期値になるんですよね。

王道パターン

さて、ここで「ちゃんとNULLで出力出来ないと困るよ!」って場合には以下のようにUser構造体の型を変えることで判別できます。

package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "github.com/k0kubun/pp"
)

type User struct {
    Name sql.NullString
    Age sql.NullInt64
}

func main() {
    db, err := sql.Open("mysql", "root:@/test")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT * FROM user")
    if err != nil {
        return
    }

    for rows.Next() {
        var user User
        rows.Scan(&user.Name, &user.Age)
        pp.Println(user)
    }
}

これを実行すると

{"Name":{"String":"tarou","Valid":true},"Age":{"Int64":12,"Valid":true}}
{"Name":{"String":"","Valid":false},"Age":{"Int64":0,"Valid":false}}
{"Name":{"String":"","Valid":true},"Age":{"Int64":0,"Valid":true}}

のようになります。
Valid というのがNULLかどうかのbool値を持っている構造体が含まれているのが分かりますね。

このままでは使い物にならないので、良い感じにNULLを返すために新たに sql.NULLXXX を含んだ構造体を作成して、ゴニョゴニョやるパターンが多く見受けられます。

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

type (
    User struct {
        Name NullString `json:"name"`
        Age NullInt64 `json:"age"`
    }

    NullString struct {
        sql.NullString
    }
    NullInt64 struct {
        sql.NullInt64
    }
)

func (ns NullString) MarshalJSON() ([]byte, error) {
    if !ns.Valid {
        return []byte("null"), nil
    }
    return json.Marshal(ns.String)
}

func (ni NullInt64) MarshalJSON() ([]byte, error) {
    if !ni.Valid {
        return []byte("null"), nil
    }
    return json.Marshal(ni.Int64)
}

func main() {
    db, err := sql.Open("mysql", "root:@/test")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT * FROM user")
    if err != nil {
        return
    }

    for rows.Next() {
        var user User
        rows.Scan(&user.Name, &user.Age)
        JSON, err := json.Marshal(user)
        if err != nil {
            continue
        }
        fmt.Println(string(JSON))
    }
}

MarshalJSON() ([]byte, error) を持つ構造体を作成することで、 Marshaler Interfaceを満たし json.Marshal 実行時に定義した MarshalJSON を実行してくれます。

これを実行した結果は・・・

{"name":"tarou","age":12}
{"name":null,"age":null}
{"name":"","age":0}

ちゃんとnullが許容されてますね!
さて、これで当初の目的は達成できたわけですが、いちいち sql.NULLXXX を含んだ構造体を作成して MarshalJSON を定義するのはヒジョーに面倒です。

そこで、 guregu/null の出番です。

guregu/null

使い方は非常に簡単。
NULL値が入った時に null.XXX 型にはnullを、 zero.XXX 型には型の初期値に変換されます。

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "gopkg.in/guregu/null.v3"
    "gopkg.in/guregu/null.v3/zero"
)

type (
    User struct {
        Name null.String `json:"name"`
        Age null.Int `json:"age"`
    }
)

func main() {
    db, err := sql.Open("mysql", "root:@/guregu")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT * FROM user")
    if err != nil {
        return
    }

    for rows.Next() {
        var user User
        rows.Scan(&user.Name, &user.Age)
        JSON, err := json.Marshal(user)
        if err != nil {
            continue
        }
        fmt.Println(string(JSON))
    }
}

こうすることで

{"name":"tarou","age":12}
{"name":null,"age":null}
{"name":"","age":0}

ちゃんと null.XXX はnullに、 zero.XXX は型の初期値になってますね。

単純にnullにしたい場合は便利なライブラリですね。
ただ、UnmarshalやMarshal時に特殊な処理をしたい場合は sql.NULLXXX を使った方法を使うのがベターでしょう。