Diary

Diary

日々学んだことをアウトプットする場として初めてみました

もっとよく標準パッケージを使おう!

これは自戒を込めてになるんですが、もっとよく標準パッケージを使おう(知ろう)という話です。

なければ探す。

例をいくつかあげてみます。

URL Encode

基本的な言語には、URL エンコード・デコードをするための標準ライブラリがあるはずです。

Bad

val encoded = path
    .replace("/", "%2F")
    .replace("?", "%3F")

Good

val encoded = Uri.encode(name)

File Path 結合

同じく基本的な言語には、Path や URL を結合する標準ライブラリがあるはずです。

Bad

var fullPath := fmt.Sprintf("%s/%s", "dir", "file")

Good

OS の区切り文字が出力されるので、windows, UNIX 系のどちらでも同じように記述できます。

var fullPath := filepath.Join("dir", "file")

また、path に間違って / 等がついていても安心です。

dir := "dir/"
file := "/file"
// dir/file
var fullPath := filepath.Join(dir, file)

おわりに

紹介しきれてないものもたくさんありますが、基本的な機能は標準パッケージにあるので、そのつもりで探すことが大切だと思っています。

標準パッケージを使った方が、安全で読みやすいです。
絶対に使っていきましょう。

Java (Kotlin) で HTTP のステータスコードの定数を使う方法

Android (Kotlin) で Retrofit を使っていた時に、HTTP のステータスコードを使いたくなりました。
直にベタ書きするのはいやだなーと思っていたところ、HttpURLConnection のクラスに用意されていました。

import java.net.HttpURLConnection

// int が求められている場所で、以下のように使える。
HttpURLConnection.HTTP_OK

bash [[ 内での <,> は文字列比較になるよ

bash で変数の評価を行う際に [[ をよく使うんですが、その中の <, > の意味を間違っていたがために、無駄に時間を使ってしまう事件がありました。
(未来の自分を含めた)皆さんには変なところで躓いて欲しくないため、こちらにメモしておきます。

[目次]

時間ない人まとめ

- [[ は bash で拡張された色々な表現が使える
- <, > は文字列の辞書順の大小を表す
- 数値を比較したいとき
  - -gt, -lt 等を使う
  - [[..]] ではなく ((..)) を使う

[[ を使った評価について

シェルスクリプトには、変数を比較するためのコマンド [ があります。
これは test コマンドと同一であり、しばしば if と合わせて以下のように使用します。

# コマンド [ の戻り値が正常(0)かどうかを判断
if [ hoge = hoge ]; then echo same string; fi
same string

if [ 3 -eq 3 ]; then echo same integer; fi
same integer

# <, > は通常のリダイレクト等に解釈されるため使用不可
if [ 3 < 5 ]; then echo left is greater than right; fi
zsh: no such file or directory: 5

また、bash には [[ という拡張された表現があるのですが、正規表現のマッチもできるため私はよくこちらを利用しています。

if [[ -n "$1" ]] && [[ -f "$1" ]]; then
    FILE="$1"
fi

if [[ "$line" =~ ^(\#+)([ ]+)([^\#]*) ]]; then
    block_level="$(echo -n "${BASH_REMATCH[1]}" | wc -c | xargs)"
    ...
fi

<, > の扱いについて

こちらの表現で何気なく <, > を使ったところ、実行時エラーにならず処理が続いていきました。

if [[ 3 < 5 ]]; then echo left is greater than right; fi
left is greater than right

ぱっと見良さそうに見えますが、多くの人にとってこれは期待値とは異なる動きをします。

もうお分かりかと思いますが、実はこれは文字列での辞書順の比較になっています!

このことについて、マニュアルの bash: [[ についてのページにはきちんと記載があります。

Expressions are composed of the primaries described below in Bash Conditional Expressions.

こちらのコンディションを参考にすると、辞書式順序で比較する、と書いてあります。

string1 < string2
  True if string1 sorts before string2 lexicographically.
string1 > string2
  True if string1 sorts after string2 lexicographically.
if [[ 30 < 5 ]]; then echo left is greater than right; fi
left is greater than right

ではどうするか

[ の時同様 -lt, -gt 等を使う

先ほどの [[どう評価されるかの条件のページを見直すと、-lt, -le, -gt, -ge を使えばいいようです。

if [[ 30 -lt 5 ]]; then echo left is greater than right; fi

一応説明すると、以下のような略語になっている、と覚えるとはやいと思います。

  • gt: Greater Than
    • >
  • lt: Less Than
    • <
  • gt: Greater than or Equal
    • >=
  • le: Less than or Equal
    • \<=

数値の表現 *3 を使う

こちらのマニュアルを再度みてみると、(( が使えそうです。

(( expression ))

The arithmetic expression is evaluated according to the rules described below (see Shell Arithmetic). The expression undergoes the same expansions as if it were within double quotes,

この中では Shell Arithmetic という数式の表現が使えるとのことなので期待できそうです。
(注意としては、表現が 0 以外のとき返り値 0 (false), それ以外の時は返り値が 1 (true) になる、ということです)

# 良さそう
if (( 30 < 5 )); then echo pien; fi
if (( 3**3 > 5 )); then echo pien; fi
pien

if (( 3 )); then echo hi; fi
hi
if (( 0 )); then echo hi; fi

最近の言語と同じ感覚で使えそうですね。

リンク

おわりに

bash のドキュメントをみるとなんでも書いてあるので申し訳なくなる(もっと仲良くなりたい)。
bash 以外の移植性考えたら、なるべく [[ とか (( とか使わないほうがいいんだろうか。

*1:..

*2:..

*3:..

Retrofit & Gson Converter でぬるぽ

以下のようなセットで API をコールしていた時に、『Non-null と思っていたのにぬるぽが発生する』ということが起きてしまいました。

原因

  • API サーバーで api-key が無効の時に status-code 200 (期待値は 400) + エラーメッセージを返してきてた
  • ステータス 200 なので Retrofit は通信成功とみなし、converer (Gson) によるデコードを開始する
    • デコードに失敗(返却値が Null)してもエラーは出ずに処理が進み、どっかでぬるぽ発生

まとめ

- 『JSON ⇒ データクラス』のデコード等では、失敗時にサイレントで null が返ってくるかもしれない
    - return のタイプを Non-null で定義してたのに。。。
  - ライブラリを使う時は注意!
    - 注意しすぎても良さが減るよな。。。

該当コード

ぬるぽ発生部

// getMembers(groupName).members がぬるのため NullPointerException
// こんぱいら(Android Studio)は無知なので気づいていない!!!
val members = repository.getMembers(groupName).members.map { it.toMember() }

Retrofit 定義部

@Module
@InstallIn(SingletonComponent::class)
object DataModule {

    // API
    @Provides
    @Singleton
    fun provideSakamichiApi(): SakamichiApi {
        val okHttpClient = OkHttpClient.Builder()
            ...
            .build()
        return Retrofit.Builder()
            .baseUrl(Constants.BASE_URL)
            .client(okHttpClient)
            // ========== Gson を converter として指定! ==========
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(SakamichiApi::class.java)
    }
    ...
}

interface SakamichiApi {

    @GET("api/v1/members")
    suspend fun getMembers(
        @Query("gn") groupName: String,
        @Query("key") apiKey: String = "",
    ): MembersDto  // ========== ここでは Non-null で data class を定義している! ==========
    ...
}

対応案

結果 2 つ目の、他の converter (Moshi) を使ってデコードさせました。 Moshi には failOnUnknown というのがありそれを利用しました。

  • Gson, Retrofit 側に『Decode に失敗したら例外を投げる』的な設定ができないか確認
    • 多分無理
  • 他の converter だと挙動変わらないかな~~っての試す
  • API の返却値をぬらぶるにして、コンパイラに教えてあげる
    • てっとりばやいが、無駄に Nullable にするのはいややな。。。

おわりに

Android Studio と Gson を信じた私がおろかでした。

git log を使って 1 日分のコード差分行数を計算

git log を使って、特定のユーザーが何行進捗を出したかを確認する例のメモです。

特に、initial commit などのコミットメッセージに代表されるプロジェクト(フレームワーク)の初期ファイルを、コミットメッセージから無視するようにしました。

  • 特定のユーザーからのコミットのみ
  • 特定のコミットメッセージを除外(下の例は case-ignore で initial commit,first commit)
  • clone さえできれば token が不要

# 1日の変更分。
# 出力例: Nov21 24
cat <(git log \
        --numstat \
        --branches \
        --since=midnight \
        --no-merges \
        --author="$(git config user.name)" |\
    awk '$1 == "Date:" { date = $3$4 } ( NF == 3 && $1 ~ /[0-9]+/ && $2 ~ /[0-9]+/ ) { print date,$1,$2 }') \
    <(git log \
        --numstat \
        --branches \
        --since=midnight \
        --no-merges \
        --author="$(git config user.name)" \
        --all-match \
        -i --grep="initial commit" |\
 awk '$1 == "Date:" { date = $3$4 } ( NF == 3 && $1 ~ /[0-9]+/ && $2 ~ /[0-9]+/ ) { print date,-$1,-$2 }') \
    <(git log \
        --numstat \
        --branches \
        --since=midnight \
        --no-merges \
        --author="$(git config user.name)" \
        --all-match \
        -i --grep="first commit" |\
 awk '$1 == "Date:" { date = $3$4 } ( NF == 3 && $1 ~ /[0-9]+/ && $2 ~ /[0-9]+/ ) { print date,-$1,-$2 }') |\
awk '{ a[$1] += $2 + $3 } END { for(date in a) print date, a[date] }'

日ごとのコミット出力

# 出力例
# 2022/01/27 19
# 2022/01/28 389
# 2022/01/31 87
# 2022/02/04 1874
# 2022/02/05 1030
# 2022/02/06 94
cat <(git log \
        --numstat \
        --branches \
        --date=format:'%Y/%m/%d' \
        --no-merges \
        --author="$(git config user.name)" |\
    awk '$1 == "Date:" { date = $2$3$4 } ( NF == 3 && $1 ~ /[0-9]+/ && $2 ~ /[0-9]+/ ) { print date,$1,$2 }') \
    <(git log \
        --numstat \
        --branches \
        --date=format:'%Y/%m/%d' \
        --no-merges \
        --author="$(git config user.name)" \
        --all-match \
        -i --grep="initial commit" |\
 awk '$1 == "Date:" { date = $2$3$4 } ( NF == 3 && $1 ~ /[0-9]+/ && $2 ~ /[0-9]+/ ) { print date,-$1,-$2 }') \
    <(git log \
        --numstat \
        --branches \
        --date=format:'%Y/%m/%d' \
        --no-merges \
        --author="$(git config user.name)" \
        --all-match \
        -i --grep="first commit" |\
 awk '$1 == "Date:" { date = $2$3$4 } ( NF == 3 && $1 ~ /[0-9]+/ && $2 ~ /[0-9]+/ ) { print date,-$1,-$2 }') |\
awk '{ a[$1] += $2 + $3 } END { for(date in a) print date, a[date] }' |\
sort

ユーザーを絞り込まない場合(上の例から author 部分を削除)

cat <(git log \
        --numstat \
        --branches \
        --date=format:'%Y/%m/%d' \
        --no-merges |\
    awk '$1 == "Date:" { date = $2$3$4 } ( NF == 3 && $1 ~ /[0-9]+/ && $2 ~ /[0-9]+/ ) { print date,$1,$2 }') \
    <(git log \
        --numstat \
        --branches \
        --date=format:'%Y/%m/%d' \
        --no-merges \
        -i --grep="initial commit" |\
 awk '$1 == "Date:" { date = $2$3$4 } ( NF == 3 && $1 ~ /[0-9]+/ && $2 ~ /[0-9]+/ ) { print date,-$1,-$2 }') \
    <(git log \
        --numstat \
        --branches \
        --date=format:'%Y/%m/%d' \
        --no-merges \
        -i --grep="first commit" |\
 awk '$1 == "Date:" { date = $2$3$4 } ( NF == 3 && $1 ~ /[0-9]+/ && $2 ~ /[0-9]+/ ) { print date,-$1,-$2 }') |\
awk '{ a[$1] += $2 + $3 } END { for(date in a) print date, a[date] }' |\
sort
  • author と grep を両方指定する場合は、--all-matchオプションも一緒に使う
  • grep のところと author のところは同じパターンを使ってそうなので、authorinvert-grep を使ったときは、検索が期待値通りにいかない!
# 失敗するパターン
git log \
 --numstat \
 --branches \
 --no-merges \
 --invert-grep -i --grep="initial commit" \
 --all-match \
 --author="gegege" |\
 awk '$1 == "Date:" { date = $3$4 } ( NF == 3 && $1 ~ /[0-9]+/ && $2 ~ /[0-9]+/ ) { print date,$1,$2 }' > res
cat res | awk '{ a[$1] += $2 + $3 } END { for(date in a) print date, a[date] }'

#
git log \
 --numstat \
 --branches \
 --no-merges \
 --author="afea" \
 --and \
 --invert-grep -i --grep="initial commit" --grep="first commit" |\
 awk '$1 == "Date:" { date = $3$4 } ( NF == 3 && $1 ~ /[0-9]+/ && $2 ~ /[0-9]+/ ) { print date,$1,$2 }' |\
 awk '{ a[$1] += $2 + $3 } END { for(date in a) print date, a[date] }'

Android のテーマアイコンの設定

Android のテーマアイコンを有効にする方法と、アプリに設定する方法を紹介します。

[目次]

テーマアイコンとは

アプリのテーマアイコンとは、以下のように『ユーザーの背景・ダークモード設定によって雰囲気を変えてくれる』アプリのアイコンのことです。
Android Developers の言葉では『themed app icons』と呼ばれています。

テーマアイコンを有効にする方法

このアイコンを表示させるには
『壁紙長押し > 壁紙とスタイル > テーマアイコン』を有効にする必要があります。

ドキュメントによると、以下の場合はテーマアイコンが表示されないようです。

  • ユーザーがテーマアイコンの設定を有効にしてない場合
    • 壁紙とスタイル
  • アプリが『monochromatic app icon』を提供していない場合
  • ランチャーが『themed app icons』をサポートしていない場合

つまり、『ユーザー設定・アプリ・ランチャー』の全てにおいて有効になっているかを確認する必要があるようです。

自アプリに設定する方法

ic_launcher.xml に、monochrome の設定を追加します。

<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background" />
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
    <!-- SVG ファイルを追加する! -->
    <monochrome android:drawable="@drawable/playground" />
</adaptive-icon>

なお、これは Android 13 からの機能になります(テーマ別アプリアイコン)。

Android 12 以下の端末
元々入っている一部のアプリ(Play Store, Settiings, Photos,...)においては有効になっていますが、基本は機能してません。

自分でカスタマイズしたアプリの見た目(中央ちょい上)
(左: Android13, 右: Android12)

標準で入っているアプリの見た目
(左: Android13, 右: Android12)

リンク

おわりに

現在、自分がインストールしているアプリでテーマアイコンを設定しているのは 1/4 くらいでした(2022/11/18)。
個人では、今後このアイコンを設定するようにしたいと思います!

<meta charset="UTF-8"> をつけてるのに文字化けする

Apache 2.4 を使ってコンテンツを置いているサーバーで、HTML の <head> 内に <meta charset="UTF-8"> をつけているのに文字化けが発生する現象が発生しました。

これは次のように content を指定したら解決しました。

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />