ogatasoが何か書きます

SWE(1年目)が勉強したことを書くヨ

jq入門(問題付き)

jqとは

jqはjsonから特定の値を抽出するのに超絶便利なコマンドラインツールです。テキスト処理を行えるCLIツールとしてはsedawkなどいろいろとありますが、JSONの処理においてはjqの右に出るものは現状いないという認識です。 本記事では、問題を交えながら実践形式でjqの基本を抑えていきます。

インストール

Macユーザでjqコマンドのインストールがまだの方は以下の方法でインストールすることが可能です。 jqはその他のOSでも利用可能ですので、Mac以外のユーザは他サイトを参考にインストールしていただけたらなと思います。

brew install jq

brewコマンドを使えなくて失敗する方は先にHomebrewをインストールする必要があります。

以下のコマンドでjqコマンドが使えることを確認しましょう。

jq --version

基本的な使い方

jqは基本的に以下のようにcurlやcatを利用してjsonの出力し、パイプを利用してjqコマンドに流し込むといった利用方法になります。

cat sample.json | jq
curl "http://localhost:7000" | jq

では早速問題を出します。

Q1 https://jsonplaceholder.typicode.com/photoscurlで叩いて、jqで表示してみてください。 以下の「答えを見る」を押すとコマンドが表示されます。

答えを見る

curl "https://jsonplaceholder.typicode.com/photos" | jq

抽出

jqの利用方法で最も多いであろう、jsonデータからの値の抽出を見ていきます。 jqでは jq 'コマンド' の形式でjsonから必要なデータのみを抽出することが可能です。 また、jq 'コマンド1 | コマンド2'のようにパイプで繋げることで複数のコマンドを適用することもできます。

配列

https://jsonplaceholder.typicode.com/photos から帰ってくるデータは配列の中に複数の要素が含まれている形式になっていました。 配列の抽出では .[n] を使ってn番目の要素をとったり、 .[n:m] で n以上m未満の範囲をとってくることができます。 また、lengthと指定すると要素の数を調べることができます。

以下の問題でも https://jsonplaceholder.typicode.com/photos を使って回答してください

Q2 0番目の要素のみを出力してください

# 期待される出力
{
  "albumId": 1,
  "id": 1,
  "title": "accusamus beatae ad facilis cum similique qui sunt",
  "url": "https://via.placeholder.com/600/92c952",
  "thumbnailUrl": "https://via.placeholder.com/150/92c952"
}

答えを見る

curl "https://jsonplaceholder.typicode.com/photos" | jq '.[0]'

Q3 2番目から4番目までの要素のみを出力してください

[
  {
    "albumId": 1,
    "id": 3,
    "title": "officia porro iure quia iusto qui ipsa ut modi",
    "url": "https://via.placeholder.com/600/24f355",
    "thumbnailUrl": "https://via.placeholder.com/150/24f355"
  },
  {
    "albumId": 1,
    "id": 4,
    "title": "culpa odio esse rerum omnis laboriosam voluptate repudiandae",
    "url": "https://via.placeholder.com/600/d32776",
    "thumbnailUrl": "https://via.placeholder.com/150/d32776"
  },
  {
    "albumId": 1,
    "id": 5,
    "title": "natus nisi omnis corporis facere molestiae rerum in",
    "url": "https://via.placeholder.com/600/f66b97",
    "thumbnailUrl": "https://via.placeholder.com/150/f66b97"
  }
]

答えを見る .[n:m]で指定した時、m番目の要素は含まないことに注意してください。

curl "https://jsonplaceholder.typicode.com/photos" | jq '.[2:5]'

Q4 配列の要素数を出力してください。

# 期待される出力
5000

答えを見る

curl "https://jsonplaceholder.typicode.com/photos" | jq 'length'

キーによる抽出

キーに対応する値を求める時には.キーという形式が利用できます。 また、複数の値を出したい場合は、カンマ(,)を使って.キー1,.キー2のように表現することができます。

配列要素から取得する場合はjq '.[n].key'のような指定が可能です。もしくは、パイプを利用して、jq 'コマンド1 | コマンド2'のようにすることもできます。

Q5 0番目の要素のtitleを出力してください。

# 期待される出力
"accusamus beatae ad facilis cum similique qui sunt"

答えを見る

curl "https://jsonplaceholder.typicode.com/photos" | jq '.[0].title'
# パイプを利用した別解
curl "https://jsonplaceholder.typicode.com/photos" | jq '.[0] | .title'

Q6 0番目の要素のtitleだけでなく、idとalbumIdも同時に出力してください。

# 期待される出力
"accusamus beatae ad facilis cum similique qui sunt"
1
1

答えを見る

curl "https://jsonplaceholder.typicode.com/photos" | jq '.[0].title,.[0].id,.[0].albumId'
# パイプを利用した別解
curl "https://jsonplaceholder.typicode.com/photos" | jq '.[0] | .title,.id,.albumId'

Q6ではidとalbumIdのどちらも整数値なので、出力結果をみた時にどちらがidでどちらがalbumIdなのかわかりにくいですよね。 キーとバリューを含んだJSON形式で出力するには中括弧({})を利用します。

以下はその例です。

curl "https://jsonplaceholder.typicode.com/photos" | jq '.[0] | {id: .id, albumId: .albumId}'
# 出力
{
  "id": 1,
  "albumId": 1
}

また、同じ要領で角括弧([])を使って配列を作ることもできます。

curl "https://jsonplaceholder.typicode.com/photos" | jq '.[0] | [.id, .albumId]'
# 出力
[
  1,
  1
]

Q7 0番目の要素のタイトルとurlから構成されるJSONを出力してみてください。

# 期待される出力
{
  "title": "accusamus beatae ad facilis cum similique qui sunt",
  "url": "https://via.placeholder.com/600/92c952"
}

答えを見る

curl "https://jsonplaceholder.typicode.com/photos" | jq '.[0] | {title: .title, url: .url}'

配列に対する便利関数

ここからの問題では以下の内容のsample.jsonを使います。

[
  {
    "id": 1,
    "name": "Alice",
    "age": 30
  },
  {
    "id": 2,
    "name": "Bob",
    "age": 25
  },
  {
    "id": 3,
    "name": "Charlie",
    "age": 35
  }
]

配列に対してはサイズを取得するlengthの他に、最大最小を求めるmaxmin、並びを変えるsortなど使いこなせると便利な機能がたくさんあります。 今回はサンプルとして配列サイズ小さなjsonファイルを利用していますが、大きなjsonファイルを扱う時にはかなり強力です。 また、map(.key)を利用すると、配列の要素を絞り込むことができます。

Q9 それぞれの要素の年齢のみを配列として抽出してください

# 期待される出力
[
  30,
  25,
  35
]

答えを見る

cat sample.json | jq 'map(.age)'

Q10 最も高い年齢を出力してください。

# 期待される出力
35

答えを見る

cat sample.json | jq 'map(.age) | max'

max_by(.key)min_by(.key)を利用するとあるフィールドの最大値、最小値をもつオブジェクトを検索して出力することができます。

Q11 最も若い人の名前を表示してください。

# 期待される出力
"Bob"

答えを見る

cat sample.json | jq 'min_by(.age).name'

まだまだいろいろあるんですがここまでにします。もっとたくさんの機能を知るには以下の記事がおすすめです。

参考:

jqlang.github.io

www.tohoho-web.com

トランザクション分離レベルと問題

トランザクション分離レベルと、各レベルで出てくる問題を整理する。

トランザクション分離レベル

まず、トランザクション分離レベルは4段階あり、「READ UNCOMMITED」→「READ COMMITED」→「REPEATABLE READ」→「SERIALZABLE」の順にトランザクションの独立性が高くなっていく。

じゃあSERIALIZABLEにすれば良いのではと思われるかもしれないが、一般にパフォーマンスとのトレードオフであることに注意を払う必要がある。

トランザクション分離レベルと発生しうる問題

以下ではトランザクション分離レベルの簡単な説明と、そのレベルでどのような問題が起きるかを解説していく。上のレベルで発生する問題は下のレベルでも発生する。

SERIALIZABLE

SERIALIZABLEは最も高い分離レベルであり、トランザクション直列化可能である。直列化可能ということは複数のトランザクションが実行されていてもそれぞれが順番に実行されたように見えるということであり、トランザクションは実行中の他のトランザクションに影響を与えることはない。SERIALIZABLEは特に独立性に関する問題を起こしません。

REPEATABLE READ

REPEATABLE READは2番目に高いトランザクション分離レベルである。同一トランザクション内で複数回の読み込みを行った時に他のトランザクションによって追加、削除されたデータが混入することがある。これをファントムリードと呼ぶ。MySQLのデフォルトの分離レベルはこれである。なお、MySQLではMVCC(MultiVersion Concurrency Control)によってファントムリードを防ぐことができる。

MVCCとは

簡単に説明すると自分より後に発行されたトランザクションの変更を読み取らないようにするために以下の2つを行っている。 -レコードにトランザクションIDを記録する - 更新、削除されるデータを取っておいて後からでも参照できるようにする。

以下が分かりやすかった。 sairoutine.hatenablog.com

READ COMMITTED

3番目に高いトランザクション分離レベル。このレベルではコミットされていないトランザクションの読み取りからは保護されるが、ノンリピータブルリード(ファジーリードとも呼ばれる)が発生する。ノンリピータブルリードでは同一トランザクションで同じレコードを複数回読んだ時に異なる結果が返ってくることがある。

READ UNCOMMITTED

最も低い分離レベルである。コミットされていない変更を他のトランザクションから読みとれてしまう(ダーティリード)。

Golandでキーボードショートカットの設定を変更する

こんにちは、ogatasoと申します。普段はJetBrainsのIDEを主に使っていますが、より使いやすくするためにキーボードショートカットを変更したいと思いましたので、その方法をご紹介します。

今回変更したいショートカットについて

まずは、変更したいショートカットについてです。デフォルトのGoLand(Mac版)では、Command + F12を押すことでメソッドリストを表示できます。これは、コードが大きい場合に構造体やインターフェースが持つメソッドを素早く把握するのに非常に便利です。また、メソッドをクリックすることで、実際に定義されている場所にジャンプすることもできます。

メソッドリスト
しかし、Fnキーを押しながらでないと流れている音楽のボリュームが上がること、そしてF12がそもそもかなり遠い位置にあることで、なかなか押すのが面倒でした。今回はこれをCommand + Mに変更したいと思います。

変更方法

本題に入ります。

まずはCommand + , を押して設定画面を表示します。キーボードショートカットに関する設定はKeymapという項目から変更できます。

Keymap設定画面
大量の設定があるため、お目当てのアクションはなかなか見つからないと思いますが、右上の虫眼鏡を押し、コマンドを入力することで検索することができます。

今回のコマンドはMain Menu -> Navigate -> Go to by Reference Actions -> File Structureにありました。

右クリックからAdd Keyboard Shortcutを押し、Command + Mを設定すれば完了です。やったー🙌

完了後の画面

Cookieについて整理する

最近、Go言語で個人開発をやっていてCookieでつまづいたことで、Cookieに対する理解度の低さを改めて感じたので少し調べていきます。

執筆前の僕のCookieの認識は「ブラウザで保存されるデータで、http通信で送信されてログインとかに利用されるやつ」なので、間違っていたら教えてください。

定義

Cloudflareの定義によると「Cookieとは、Webサーバーが生成してWebブラウザーに送信する小さな情報ファイルです。Webブラウザは、受け取ったCookieを所定の期間、またはWebサイト上にユーザーのセッションがある間保存します。また、ユーザーは次回以降Webサーバーにリクエストする際に、関連するCookieを添付します。」とのことです。なので、最初の認識で大体あっている。

https://www.cloudflare.com/ja-jp/learning/privacy/what-are-cookies/#:~:text=Cookie%E3%81%A8%E3%81%AF%E3%80%81Web%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC,Cookie%E3%82%92%E6%B7%BB%E4%BB%98%E3%81%97%E3%81%BE%E3%81%99%E3%80%82

Cookieを保存する流れ

サーバーが発行したJWTなどをCookieに保存する場合、サーバーはHTTPリクエストのヘッダに"Set-Cookie"というフィールドを含み、その中でkey=value;の形で保存して欲しいCookieを指定することができる。また、「;」の後には属性が続いていく。複数のCookieを設定する場合、それぞれのCookieに対して別々のSet-Cookieフィールドが含まれる。つまり、3種類のキーバリューを保存して欲しい場合はSet-Cookieフィールドを3つ指定する。

ちなみにGo言語で設定するならhttpライブラリを使ってこんな感じになる。

http.SetCookie(w, &http.Cookie{
    Name:     "jwt",
    Value:    tokenString,
    Secure:   true,
    HttpOnly: true,
    Path:     "/",
    Expires:  time.Now().Add(60 * 60 * 24 * time.Minute),
    SameSite: http.SameSiteNoneMode,
})

一方で、クライアントがサーバーにリクエストを送る際には"Cookie"というフィールドにキーバリューを詰めて送る。ここで、キーバリューは「;」で分けられる。

属性

この属性がよくわからなかったので調べていく。属性次第ではSet-Cookieを指定してもWebブラウザに保存されなかったり。軽く紹介するだけなので、以下を見た方が多分良い。

developer.mozilla.org

Secure

HTTPSでのみ利用できるようにする。今時基本的にHTTPSなので指定しておいて損はない気がする。ローカルホストだと動かなくなるので注意。 (追記: ブラウザによってはlocalhostでのSecure属性は無視してくれるらしい。やってみるとchromeはそうっぽい。) teratail.com

HttpOnly

この属性が指定されたクッキーはJavaScriptから読まれない。XSS攻撃の対策になる。JSで利用しないならtrueにしておいてよさそう。

Domain

Cookieを受信できるホストを指定する。指定しない場合はサーバと同じドメインをホストとし、サブドメインは除外される。指定した場合はサブドメインも含まれる。例えば、Domain=mozilla.org を設定すると、developer.mozilla.org のようなサブドメインも含まれる。

Path

この属性が指定されると、Pathに一致するページでのみリクエストにクッキーが含まれる。 "/"であれば全てのパスに一致するし、"/users"としたら"/message"には一致せず、クッキーは送信されない。

SameSite

SameSite属性では異なるサイト間でのリクエストでCookieを送るべきかそうでないかを指定できる。これは、CSRFに対しての防御となる。SameSiteの設定にはStrict, Lax, Noneの3種類がある。デフォルトはLax。

Strict ... 元サイトからのリクエストに対してのみCookieを送る。外部サイトからのリダイレクトを含む、他のサイトからのリクエストではCookieは送信されない。

Lax ... 元サイトに対してのみCookieを送るが、外部サイトからリンクを辿った場合にもCookieを送る

None ... 異なるサイト間でも送信できる(ただし、Secure属性を設定する必要がある。)

Expires および MaxAge

Cookieを残す期間を指定することができる。両方指定した場合はMaxAgeが優先される。ちなみに無期限にすることはできないらしい。

Go言語のセンチネルエラーってなに?

初めてのGo言語を読んでいて、初めてみる「センチネルエラー」という単語に出会ったので、簡単に説明してみようと思います。

センチネルエラーとは、Goで時々見るエラーハンドリングのパターンの一つである(ちなみに、sentinelには見張りや衛兵といった意味があるらしい)。

単純にerr != nilでエラー判定するのではなく、err != zpi.ErrFormat のような比較をif文の条件にいれ、より具体的なエラーの種類を明示することで、特定エラーへの対応を行うことができる。

また、全てのセンチネルエラーはその変数の名前をErrから始める慣習がある。ただし、io.EOFは標準ライブラリの中では例外的にErrから始まらないセンチネルエラーである。

例えば、Goの標準ライブラリのarchive/apiではパッケージレベルで以下のようなエラーが定義されている。https://cs.opensource.google/go/go/+/refs/tags/go1.22.0:src/archive/zip/reader.go;l=27

var (
    ErrFormat       = errors.New("zip: not a valid zip file")
    ErrAlgorithm    = errors.New("zip: unsupported compression algorithm")
    ErrChecksum     = errors.New("zip: checksum error")
    ErrInsecurePath = errors.New("zip: insecure file path")
)

以下はその活用例(初めてのGo言語より)。

nonZipFile := bytes.NewReader(data)
_, err := zip.NewReader(nonZipFile, int64(len(data)))
if err == zip.ErrFormat {
    fmt.Println("ZIP形式ではありません")
}

こうしてみると、どのような種類のエラーが発生しうるのかが一目でわかってとても良さげ。というか、こういうのは他の言語でも似たようなものを見かけることはあるのでGo特有のエラーパターンではない気がする。

センチネルエラーのデメリットとして、柔軟性がなくなってしまうことが挙げられるため、その利用は必要最小限に抑えておくべきである。

Go言語でJWTを生成してみる

JWT認証はログインシステムの実装としてポピュラーですが、今回はGo言語でのJWTの生成にちょっとつまづいてしまったため、備忘録として書き残しておきます。以下のライブラリを利用してJWTを生成していきます。

github.com

JWTとは

初めに、JWTとは何かについて整理しておきます。JWTとはJson Web Tokenの略で、読み方は「ジョット」とからしいのですが、私はそのまま「ジェーダブリューティー」と呼んでしまっています。実物をお見せするとこんな感じです。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJncmVldGluZ3MiOiJIZWxsbywgV29ybGQhIn0.6Yh5DR_8xnjywt9K_ITKDip5r2wKa0_TYMFXJ5iXDaU

ピリオド(.)によって3つの部分に分かれていることを確認することができます。最初の部分はヘッダとして暗号アルゴリズムの名前およびトークンのタイプ(基本的には大文字で"JWT"と指定するのが推奨されている)が保存されており、真ん中の部分はペイロードが、それぞれBase64urlエンコード形式で保持されています。最後の部分はヘッダとペイロードを暗号化することで生成される署名になっています。JWTの中身は以下のようなデコーダーで確認することができます。 web-toolbox.dev

JWTはRFC 7519で仕様定義されているので、より詳しく知りたい方は読んでみるのも良いでしょう。 tex2e.github.io

JWTを作成する

JWTは以下のコードで生成できます。Claimsはペイロードに相当する部分で、jwt.NewWithClaimsでClaims付きでJWTを作ります。その後、token.SignedString([]byte("secret"))で署名部分を加えてJWTを完成させます。これはサンプルなので鍵を"secret"としていますが、本番運用では$ openssl rand -hex 32などで生成した乱数を利用するのが好ましいでしょう。

func createJWT() string {
    claims := jwt.MapClaims{
        "greetings": "Hello, World!",
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenWithSignature, _ := token.SignedString([]byte("secret"))
    return tokenWithSignature
}

JWTを検証、およびペイロードの中身を確認

jwt.Parseを利用してトークンをパースします。第二引数の関数にはパースされたトークンが渡され、初めにトークンの暗号方式が生成時に利用したjwt.SigningMethodHMACと同じものかどうかを検証したのち、鍵を渡しています。 ペイロードtoken.Claimsにあり、jwt.MapClaimsに型アサーションした上で中身を取ることができます(私はここがわかりにくくてつまづいていました...)。また、JWTの署名が正しいかどうかはtoken.Validで確認することができます(確認を忘れるとJWTの意味がなくなってしまうので要注意)。試しに以下のコードの"secret"を他の文字列に変えてみて、falseになっていればきちんとtoken.Validが機能していることを確認できます。

func verifyJWT(tokenString string) {
    token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
        }
        return []byte("secret"), nil
    })
    claims := token.Claims.(jwt.MapClaims)
    fmt.Println(claims["greeting"]) // Hello, world!
    fmt.Println(token.Valid) // true
}

脳に余裕をもたらす方法について 世界一流エンジニアの思考法 3章より

はじめに

世界一流エンジニアの思考法を読んでいるが、学びが非常に多い。3章の「脳に余裕を生む情報整理・記憶術」は特に自分に刺さりまくったのと、「学びをブログに書け!」というアドバイスが含まれていたので、早速3章の学びについて軽くまとめてみた。

3章概要

3章は主に脳の負担を軽減する方法について書かれている。具体的には、コードを素早く読む方法や記憶力を高める方法などである。私は頭の回転も記憶力もそれほど良くない方だと自己評価していることもあることから、全部の章の中でも3章はかなり有益だと感じた。

コードを早く読むには

私はコードリーディングには結構時間がかかる上、結局よくわからないままそれっぽい関数を見つけて雰囲気で作業しているが、筆者も同様の課題を抱えていたようである。結論としては、実装詳細についてはそれを書いたエンジニアを信用してコードは極力読まず、クラスの役割やインターフェイスなどに集中するのが良いそう。また、不明な点は人に聞くのが早い。(これができるのは多分きちんとしたアーキテクチャでソフトウェアが構築されていることが前提ではある。筆者はマイクロソフトで働いているので、かなりコードは綺麗なんだろうなぁ。)

タスクの選び方

タスクにはそれぞれ難易度があり、1. 調べずに解決できる, 2. ググりながら解決できる, 3. 解法を知らないがスパイクソリューションで解決できそう, 4. 自分一人では無理もしくはかなり時間がかかる の4つに大別できる。スパイクソリューションという単語は聞いたことがなかったが、情報収集を目的としてお試しでプログラムを作ってみるという手法らしい。1 筆者は「このレベル4の仕事ができるようにならねば」と焦っていたが、現在では生産性を上げるには2を1にしていくのを目指すのが良いと考えをあらためている。また、そうしていると3が2になることもあり得る。その他、作業をやっていてしんどくなるのはそれが自分のできるレベルを超えている可能性があり、自分のできるレベルを見積もることが大切であるとも記述されている。

アウトカム至上主義をやめる

自分もそうだが、1日の終わりに何かを成し遂げていないと「自分今日何もやってないじゃん!」と辛くなってしまう。ただ、実際には1つの作業は丁寧にやって、しっかりと理解することが長期的に見て生産性を向上させる。

マルチタスクをやめる

まずマルチタスクはやめた方がよく、4時間は自分の作業に集中する時間を取るべきとのこと。自分の時間を取るにはteamsやスラックを閉じれば良い。

記憶力を高める

記憶力の低さの原因は理解の浅さにある。理解を深める方法として以下のようなものが紹介されている。

  • 学んだことはブログに書く

  • コーネルメソッドで学びを整理し、しっかり復習する

  • 頭の中で整理し、言語化できるようにする。この時、実際にはやらないとしても後で人に説明することを意識しておくと効果が上がる。

  • ビジュアルイメージを構築する

物事をできるようになるためには理解、記憶、反復が必須である。理解していないとコントロールできないし、記憶していないと思い出すために時間を費やすことになってしまう。また、記憶、理解したことをいつでも取り出せるように反復できるようにしておくことも大切。これらが脳の負荷を軽減し、生産性を高めると言える。