marble log

Webエンジニアの技術ブログです

各社の認可エンドポイント仕様

認可エンドポイント仕様

Google Amazon Microsoft Yahoo Japan
method GET GET GET GET・POST
path /o/oauth2/v2/auth /oauth2/authorize /oauth2/v2.0/authorize /yconnect/v2/authorization

RFCで取り上げられているパラメータの必須・推奨仕様

(〇:必須、空:オプション、×:未対応)

parameter Google Amazon Microsoft Yahoo Japan GitHub
client_id
redirect_uri
response_type ×
scope
state 推奨 推奨しない 推奨
code_challenage × 推奨 ×
code_challenge_method × 推奨 ×

References

WSL2(Ubuntu)上にIntellijをインストールする

概要

WSL2(Ubuntu 20.04)にIntellijをインストールするための手順。 もともとはWindows上にインストールしたIntellijから、WSL上のコードやJVMを利用していたが、Windows - WSL間のIO周りが非常に重くストレスがたまったため、IntellijもWSLにのせることにした。

環境情報 - Windows 11 Home 2022H2 - WSL2 (Ubuntu 20.04) - Intellij IDEA 2023.2.4

インストール

pleiades.io

スタンドアローンインストールを参考に、Intellijをダウンロードし/opt配下に展開する。 正しく展開できれば以下のコマンドでIntellijが起動する(バージョンはよしなに)。

$ /opt/idea-IC-232.10203.10/bin/idea.sh

便利のためシンボリックリンクをはる。

$ sudo ln -s /opt/idea-IC-232.10203.10/bin/idea.sh /usr/local/bin/idea
$ idea  # Intellijが起動する

(本当はsnapを使ってインストールしたいが、WSL2 Ubuntu20.04はsnapdが起動しないのであきらめた。)

日本語対応

astherier.com

WSL2のUbuntuには日本語フォントがデフォルトでインストールされていないので、エディタやコマンドラインの日本語が文字化けしてしまう。Windowsのフォントを参照する形で日本語フォントを取り込む。

キーボード配列の変更

GUIアプリケーションをUS配列キーボードで操作したい場合は、以下のコマンドを打つ。

$ setxkbmap -layout us

「PythonでJavaのOptionalクラスを実装する」のユニットテスト

ユニットテストも書いたので載せておく。 Optionクラスはsrcディレクトリ配下のoption.pyに実装している。

from unittest import TestCase

from src.option import Option


class TestOption(TestCase):
    def test_値の存在チェックができる(self):
        for value, expect in [(1, True), (None, False)]:
            with self.subTest():
                target = Option(value)

                actual = target.is_present()

                self.assertEqual(actual, expect)

    def test_値を取得するときに分岐処理ができる(self):
        for value, expect in [(1, 1), (None, 2)]:
            with self.subTest():
                target = Option(value)

                actual = target.or_else(2)

                self.assertEqual(actual, expect)

    def test_マッピング処理ができる(self):
        for value, expect in [("hoge", "piyo"), (None, None)]:
            with self.subTest():
                target = Option(value)

                actual = target.map(lambda x: "piyo").get()

                self.assertEqual(actual, expect)

    def test_フィルター処理ができる(self):
        for value, expect in [("hoge", "hoge"), ("piyo", None), (None, None)]:
            with self.subTest():
                target = Option(value)

                actual = target.filter(lambda x: x == "hoge").get()

                self.assertEqual(actual, expect)

PythonでJavaのOptionalクラスを実装する

Pythonで型ヒントが使えるようになってから、mypyなどの型の静的解析の活用もあいまってPythonでプロダクションレベルの開発がしやすくなった。 ただ、JavaのOptionalクラスのような、Noneの扱う便利クラス/メソッドは言語レベルでは提供されていないため、自前かライブラリを利用する必要がある。

型ヒントを活用して、JavaのOptionalクラスの一部の機能を実現するOptionクラスを実装した。Optionalは型ヒントですでに使用されているため、クラス名はOptionとした。

from typing import Callable, Generic, Optional, TypeVar

T = TypeVar("T")
U = TypeVar("U")

"""JavaのOptionalクラスを実現するクラス
"""


class Option(Generic[T]):
    """コンストラクタ

    JavaのOptionalクラスが持つof、ofNullableメソッドの機能をコンストラクタで担う。
    Javaのofメソッドに相当する、None非許容のコンストラクタは作らない。

    :param value: 値
    :return: インスタンス
    """

    def __init__(self, value: Optional[T]):
        self.__value = value

    """値を返すメソッド。JavaのOptionalクラスと異なり、値がNoneでも例外は投げない。

    :return: 値
    """

    def get(self) -> Optional[T]:
        return self.__value

    """値が存在する場合はTrue、そうでない場合はFalseを返す。

    :return: 値が存在する場合はTrue、そうでない場合はFalse
    """

    def is_present(self) -> bool:
        return self.__value is not None

    """値が存在する場合は値を返し、そうでない場合はotherを返す。

    :param other: Noneの場合に返す値
    :return: Optionの値かother
    """

    def or_else(self, other: T) -> T:
        return self.__value or other

    """マッピング関数をその値に適用した結果をOptionで返す。valueがNoneの場合は空のOptionを返す。

    :param mapper: マッピング関数
    :return: マッピング関数を適用した結果
    """

    def map(self, mapper: Callable[[T], U]) -> "Option[U]":
        if self.__value is None:
            return Option(None)
        return Option(mapper(self.__value))

    """値が存在し、その値が与えられた述語に一致する場合は、値を記述するOptionを返し、それ以外の場合は空のOptionを返す。

    :param predicate: フィルター関数
    :return: フィルターを適用した結果
    """

    def filter(self, predicate: Callable[[T], bool]) -> "Option[T]":
        if self.__value is None:
            return Option(None)
        return self if predicate(self.__value) else Option(None)

Jest メモリリークと「heap out of memory」エラーの対処

Jestのメモリリーク

JavaScriptのテスティングフレームワークであるJestは、長らくメモリリーク、過大なメモリ消費が問題となっている。メモリリークの影響により、テストを実行するCIが低速になったり、「heap out of memory」エラーが出て異常終了したりする。この問題は未解決で、いくつか提示されている対処療法でごまかす必要がある。

再現環境

もともと以下の環境でテストを実施していて、ほとんどheap out of memoryになることはなかった。

対象 情報
Node v14
Jest v26
プリプロセッサ ts-jest
CI環境 CPU 8 Core, Memory 16GB

上記の状態からNode、Jestそれぞれアップデートを試したところ、いずれのアップデートでもCI実行速度の低下とメモリ消費の増大を確認した。 - Jest v26からv27へのアップデート - Node 14から18へのアップデート

その他の再現情報

Jestやts-jestのissueにも同じ現象が多く報告されている。 - [Bug]: Memory consumption issues on Node JS 16.11.0+ · Issue #11956 · jestjs/jest · GitHub - Module caching memory leak · Issue #1967 · kulshekhar/ts-jest · GitHub

対処療法

前述の通りメモリリークを根本解決する方法はないので、対処療法でごまかす必要がある。

ヒープ領域を広げる

Nodeのmax-old-space-sizeオプションを使うと、確保するヒープ領域を指定できる。Node v12以降、Nodeがデフォルトで確保するヒープサイズは実行環境に応じて変化する。もし実行環境のメモリ量が少ないのであれば、オプションでヒープ領域を多めに確保すると、out of memory問題は解決する可能性がある。

以下はpackage.jsonスクリプトでヒープ領域を指定する例。

"test": "NODE_OPTIONS=\"--max-old-space-size=4096\" jest"

テストを分割する

shardオプション(Jest CLIオプション #shard)を使うとテストケースを分割できる。CIでテストを回す際に、shardオプションを使用し別々のジョブで並列実行することで、out of memory問題の緩和とテスト実行時間の短縮が見込まれる。

以下はpackage.jsonスクリプトでテストを3分割する例。

"test1": "jest shard=1/3"
"test2": "jest shard=2/3"
"test3": "jest shard=3/3"

workerのメモリ使用量を制限する

Jest v29から追加されたworkerIdleMemoryLimitオプション(Jestの設定 #workerIdleMemoryLimit)を使うと、workerが使用するメモリ量を制限できる。メモリ量は1GiBのように指定できるほか、0.2と書けば実行環境の20%を指定できる。

以下はpackage.jsonスクリプトでworkerのメモリ使用量を制限する例。

"test": "jest --workerIdleMemoryLimit=0.2"

【Kotlin】LocalDateにrangeToを実装

import java.time.LocalDate

internal class LocalDateProgression(
    override val start: LocalDate,
    override val endInclusive: LocalDate,
    private val stepDays: Long = 1
) :
    Iterable<LocalDate>, ClosedRange<LocalDate> {

    override fun iterator(): Iterator<LocalDate> = LocalDateIterator(start, endInclusive, stepDays)

    infix fun step(days: Long) = LocalDateProgression(start, endInclusive, days)

    internal class LocalDateIterator(
        startDate: LocalDate,
        private val endDateInclusive: LocalDate,
        private val stepDays: Long
    ) :
        Iterator<LocalDate> {

        private var currentDate = startDate

        override fun hasNext() = currentDate <= endDateInclusive

        override fun next(): LocalDate {
            val next = currentDate
            currentDate = currentDate.plusDays(stepDays)
            return next
        }
    }
}

「コードは資産ではなく負債」である

単体テストの考え方/使い方」と「Googleのソフトウェアエンジニアリング」にコードは負債であると明言されており、印象に残った。それぞれの本の文脈とともに残しておく。

単体テストの考え方/使い方

単体テストの考え方/使い方には、1.2.1 なぜ、単体テストを行うのか?のコラムに「コードは資産ではなく負債」と太字で強調されている。

コラムはテストコードがもたらすコストについて、「エンジニアのなかには、プロダクトコードと異なりテストコードは所有するコストが低いと考える人がいるが、それは誤りである」と言及している。テストコードもプロダクトコードと同様に保守が必要であり、最低限に保つに越したことはない。不必要なテストケースを削減する大きなモチベーションだ。

Googleのソフトウェアエンジニアリング

Googleのソフトウェアエンジニアリングにも、9.1 コードレビューのフローに「コードとは債務である」というコラムが掲載されている。この本にはGoogle培ったソフトウェアエンジニアリングの知見や哲学が、文化、プロセス、ツールの観点からまとめられている。9章はコードレビューについて書かれていて、「コードとは債務である」というコラムはこれに含まれる。

コラムは特にコードの重複がもたらすコストについて言及している。「Google規模のコードベース内なら、これから実装しようとするコードをきっと誰かがすでに実装しているはずであり、コードの重複を見つけコードベースへの混入を防ぐことが望ましい」という。Googleにはこれらを検出するツールもあると後の章で紹介されている。 このコラムはコードレビューの文脈で語られているため、ここではコードだけでなく、レビューの重複も問題視していると考えられる。レビューも工数に含め、工数最適化の対象にする点は勉強になる。

ただしGoogleはコードベースが超大規模という特殊な事情がある上に、むやみに重複を消すとかえって依存関係の問題をもたらす可能性もあるため、鵜呑みにすることは避けたい。

事業にとって資産になり得るのはコードではなくプロダクト

コード(負債)は存在するだけで保守作業(利子)が発生する。同じ振る舞いを実現する2つのコードがあるのであれば、少ない方がよい(もちろん可読性は高ければならない)。

事業にとって資産になり得るのはコードではなくプロダクトである ― コードをビルドし、デプロイし稼働させたプロダクトであり、プロダクトが利益をもたらす。借り入れを少なくし、利益を生み出すように心がけたい。