gitのdiff-highlightを使い始めた

git log -pgit diff などで差分を見るとき、行単位での追加/削除は表示されるが、行の中のどこが変わったのかは表示してくれない。例えば行の中の一単語を書き換えただけで、しかもその行が長い場合、どこに差分があるのか目で探すのが結構大変だった。
しかし先日、 diff-highlight という便利なモジュールが提供されていることを知り、早速導入してみた。

diff-highlightとは

github.com

gitコマンドの、行単位での差分を探す動作のポストプロセスとして実行され、同じ行の中の差分をハイライトしてくれる。 例えば、行の一部分だけ変えたときの git diff は、今までこんな感じだった。

f:id:Udomomo:20191201171033p:plain

それがこうなる。差分がわかりやすい。

f:id:Udomomo:20191201171052p:plain

diff-highlightの設定

この機能は gitコマンドに同梱されているため、インストールは不要。設定作業のみで使える。
まず .gitconfig に以下を追記する。

[pager]
    log = diff-highlight | less
    show = diff-highlight | less
    diff = diff-highlight | less

ただしこの設定を有効にするには、 diff-highlight 自体にPATHを通す必要がある。
Macのhomebrewで git をインストールした場合、バージョンを更新すると diff-highlightディレクトリも変わってしまう。その場合、 git-coreシンボリックリンクに対して /usr/local/bin からさらにシンボリックリンクを貼るという方法で回避できる。

sudo ln -s /usr/local/share/git-core/contrib/diff-highlight/diff-highlight /usr/local/bin/diff-highlight

この方法は以下の記事で知った。 qiita.com

これで diff-highlight が使えるようになる。 .gitconfig に設定を加えたことで、 git loggit show コマンドでも diff-highlight が有効化されている。

また、普段 tig を使っている場合、2.2.1以降であれば .tigrc で以下の設定をすると、 tig でも diff-highlight が使えるようになる。

set diff-highlight = true

JJUG CCC 2019 Fallに参加しました

ccc2019fall.java-users.jp

半年に一度のJavaカンファレンスです。個人的にこの半年は扱う技術範囲が広がり、自分の興味や課題意識も大きくなったので、今回は気になるセッションが増えました。
参加したのは以下の3つです。

入門 例外

github.com

例外処理についてのセッションです。今年の6月頃に、 System.exitRuntimeException の違いを問われて答えられなかった苦い思い出があるので、体系的に例外処理を学べる良い機会でした。Javaの例外がオブジェクトとして扱われていることのメリットを初めて実感できた気がします。
時間が足りず、検査例外の話を聞けなかったので、改めて復習するつもりです。

Reliability Engineering Behind The Most Trusted Kafka Platform at LINE

業務でKafkaを触る機会が増えそうなので参加しましたが、Kafka以上に「問題の原因を根本的に解明することの重要性」の方に多くの時間が割かれています。Produce APIのレスポンスタイム遅延の原因を探るため、最終的にサーバの物理レイヤーまで潜っていった話に、そこまでやるのかと終始圧倒されっぱなしでした。
調査に多くの時間を割くことができた理由が気になったのですが、「クライアントから見えるような大きな影響がまだ出ていない状況だったから」とのことでした。その判断をするうえでも、SLOの指標を作り可視化することが大事だと気づきました。

JVMs in Containers: Best Practices

jjug-cfp.cfapps.io

主にJavaアプリケーションをDockerで動かすときのimageサイズの話でした。Alpine程度であれば使ったことがありましたが、jlinkを使って必要最小限のライブラリしか入っていない実行環境を作る方法などは初めて知りました。また、JDK自体も新しいバージョンになるほどサイズダウンやコンテナ内実行の考慮がなされているため、バージョンを上げるだけでもサイズを小さくできるそうです。
このセッションはデモが多く、実際にDockerfileや出来上がったコンテナのサイズを確認できたことで、サイズダウンの効果を実感できました。

今回は春と比べて、超初心者向けから上級者向けまでセッションが幅広くなったように思います。自分もJavaの中で全然理解できていない分野がいくつかあり、基礎から学び直した方がいいと思っていたところだったので、とても良い機会になりました。
今回は他にも気になるセッション(Maven・DIコンテナ・ロギングなど)がいろいろあったのですが、時間がかぶっているものが多く参加できませんでした。今日の熱気が冷めないうちに資料を読んでおきたいです。

KubernetesのServiceを触って学ぶ

仕事でKubernetesを扱うことが不可欠になりつつあるので、最近重点的に学んでいる。Podの立て方は理解したが、Serviceというものが何をするのかわからなかったので、実際に動かして学んでみる。

今回使っているのは、Docker DesktopのKubernetes。Docker DesktopにはKubernetesが付属されており、 Preference 内の Kubernetes から enable Kubernetes を選べばよい。Docker Desktopが起動する際に、KubernetesのClusterも立ち上がるようになる。

f:id:Udomomo:20191117175710p:plain

また kubectl コマンドも同梱されており、 初期状態では docker-desktop というローカルのClusterを指している。

kubectl config get-contexts
CURRENT   NAME                 CLUSTER          AUTHINFO         NAMESPACE
*         docker-desktop       docker-desktop   docker-desktop 

Serviceの役割

Serviceとは、PodやReplicaSetに対するサービスディスカバリを提供である。KubernetesのPodはそれぞれ独自のIPアドレスを持つが、Podは一度死んだら復活せず、新しく別のPodが作られIPアドレスも変わる。その場合でも、Pod間での接続を担保するためにServiceがある。
Serviceは、Podの論理的集合に対してCluserIPという仮想のIPアドレスを割り当てる。そのIPアドレスにアクセスすることで、リクエストがkube-proxyを通じて各Podに到達できるようになる。仮にPodが死んで新しくなってもClusterIPは変わらないので、相手となるPodの状態を気にせずにリクエストを送ることができる。

Serviceを作る

まずはPodとServiceを立ててみる。設定ファイルはこちらの記事のものをお借りしつつ、少し変えている。

qiita.com

# php-apache-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sample-web-pod
  labels:
    app: http-app
spec:
  containers:
    - name: web-container
      image: php:7.0-apache
      ports:
        - name: http-port
          containerPort: 80
      volumeMounts:
        - name: documentroot
          mountPath: /var/www/html
  volumes:
    - name: documentroot
      hostPath:
        path: /home/username/containers/web/html
#php-apache-service.yaml
kind: Service                                                               
apiVersion: v1
metadata:
  name: http-service
  labels:
    app: http-app
spec:
  selector:
    app: http-app
  ports:
    - name: "service-port"
      protocol: "TCP"
      port: 8080
      targetPort: http-port
  type: ClusterIP

Serviceの設定ファイル内では、 selector の部分でPodのLabelを指定している。 app: http-app というラベルは、Podの設定ファイルの metadata.labels で指定してある。
また、 targetPorthttp-port となっているが、これもPodの設定ファイルの中で定義した名前である。

これを使ってオブジェクトを作り、状態を見てみる。

kubectl create -f php-apache-pod.yaml 
pod/sample-web-pod created

kubectl create -f php-apache-service.yaml 
service/http-service created
kubectl get pod --show-labels
NAME             READY   STATUS    RESTARTS   AGE     LABELS
sample-web-pod   1/1     Running   0          3h34m   app=http-app

kubectl get svc         
NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
http-service   ClusterIP   10.99.212.152   <none>        8080/TCP   6s
kubernetes     ClusterIP   10.96.0.1       <none>        443/TCP    5h41m

http-service が作られ、ClusterIPが 10.99.212.152 になっている。Cluster内部ではこのIPアドレスを使うことでPodにアクセスできる。
それを確認するために、さらに詳しく調べてみる。

kubectl describe service http-service
kubectl describe svc http-service
Name:              http-service
Namespace:         default
Labels:            app=http-app
Annotations:       <none>
Selector:          app=http-app
Type:              ClusterIP
IP:                10.99.212.152
Port:              service-port  8080/TCP
TargetPort:        http-port/TCP
Endpoints:         10.1.0.5:80
Session Affinity:  None
Events:            <none>

ubectl get pods -o wide
NAME             READY   STATUS    RESTARTS   AGE     IP         NODE             NOMINATED NODE   READINESS GATES
sample-web-pod   1/1     Running   0          5h49m   10.1.0.5   docker-desktop   <none>           <none>

Serviceの Endpoints が、PodのIPアドレスに等しいことがわかる。

これをふまえて、Podの中にいるApacheサーバにアクセスしてみる。ClusterIPはClusterの中からしか使えないのでどうするか迷ったが、調べるとこのClusterの中に使い捨てのPodを立ててリクエストを送るやり方が一番楽そうだった。

kubectl run testpod --image=centos:6 --restart=Never -i --rm -- curl -s http://10.99.212.152:8080/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access /
on this server.<br />
</p>
<hr>
<address>Apache/2.4.25 (Debian) Server at 10.99.212.152 Port 8080</address>
</body></html>
pod "testpod" deleted

Apacheサーバの設定を何もしていないので403になってしまうが、リクエストを無事に届けることはできた。

大量のファイルを一気に消したい時はxargsを使う

先日、テスト実行で生成された大量のログファイルを消そうとしてうまくいかず、少し時間を使ってしまったので書いておく。仮に log0000.json から log9999.json まで10,000ファイルあるとする。
これらを一気に消そうと思い普通にrmを使ったところ、実行に失敗した。

$ rm testsort_*.json
argument list too long: rm

これはエラーの通り、アスタリスクを展開したときのコマンドが長くなりすぎて、システムで処理できないということ。どうすればいいか悩んだが、ファイルを1つずつ削除すれば良い話。
例えばxargsを使えば以下のようにできる。

$ find . -name "log*.json" | xargs rm

(2020/11/16: 以下部分の誤った記述を修正)
xargsは、標準入力として受け取ったデータを1つずつ逐次実行していく。そのため、実行中にディレクトリを覗くとファイルが除々に減っていくのがわかる。forループを使っても良いが、その場合 find コマンドの実行が終了してからでないと削除に移らない。時間がかかる処理の場合正しく進行しているのかわかりにくいため、個人的にはxargsの方が好み。

上の話は xargs -I の場合。デフォルトのxargsは、受け取った全ての引数をまとめてコマンドに渡す。ただし、コマンドが取れる引数の数の限界になるまで引数をセットしたらコマンドを1回実行する。余った引数は、2回目、3回目のコマンドに渡されて実行される。
このため、今回のように大量のファイルを消す場合、可能な限り最大数の引数を取ってrmが何度か実行される。実行中にディレクトリを覗くとファイルが除々に減っていくのがわかる。xargsを使うことで、rmの実行回数が最小限に抑えられ、パフォーマンス的にも良い。
以下、xargsのmanより。

The command line for command is built up until it reaches a system-defined limit (unless the -n and -L options are used). The specified command will be invoked as many times as necessary to use up the list of input items. In general, there will be many fewer invocations of command than there were items in the input. This will normally have significant performance benefits.

styled-componentsが反映されないときは詳細度を調べてみよう

React + TypeScriptで作られたコードにstyled-componentsを適用する作業を進めている。基本はSCSSファイルで指定されたデザインをそのままstyled-componentsに移すだけなのだが、正しく移植したはずなのにデザインが乱れることが最近よく起こっていた。

//元のscssファイル
input.hoge {
    float: left;
    width: 50%;
    margin-right: 10px;
}
//styled-components
const Hoge = styled.input`
    float: left;
    width: 50%;
    margin-right: 10px;
`;

開発者ツールで見てみると、外部のCSSライブラリでもinput要素のデザインが指定されていた。

input[type=text] {
    width: 100%;
    ....
}

そして、SCSSファイルを使った場合は、SCSSファイル内で定義した width: 50% が正しく反映されるが、styled-componentsにした場合は外部CSSライブラリの width: 100% が反映されてしまうことがわかった。

これは、styled-componentsでは通常 .123abc のようにランダム生成されたクラス名1つによる指定として style タグをhtmlに挿入するためである。これに対し、今回のSCSSファイルでは input.hoge のように、クラス名のみならずinput要素も指定されていた。このため、styled-components化したことでCSS要素指定の詳細度が下がり、外部ライブラリの input[type=text] の方が優先されてしまっていた。
現にCSSの詳細度を計算できるサイトで調べてみると、外部CSSファイルとSCSSファイルの詳細度は同じ(->グローバルな外部CSSファイルよりも特定クラスが対象のSCSSファイルが優先される)なのに対し、styled-componentsの場合だけ詳細度が下がっていることがわかる。

f:id:Udomomo:20191106145130p:plain

対応策としては、まずstyled-components内で & を複数使い、クラスを重ねて指定する方法がある。この方法はstyled-componentsの公式ドキュメントにも載っている。

www.styled-components.com

//styled-components
const Hoge = styled.input`
    && {
        float: left;
        width: 50%;
        margin-right: 10px;
    }
`;

上記のようにすると、 & 1つごとにクラス名の指定1つに変換され、コンパイル後の style タグ内では .123abc.123abc のように、同じクラス名で二重に指定するようになる。& を3つ並べればクラス名を三重に指定できる。これはCSSの詳細度を上げるためによく使われるテクニックで、SCSSファイル内でもこの記法を使うことができる。

もう1つの方法として、ターゲットの要素の周囲にWrapper要素を作り、Wrapperのstyled-componentsの中で子要素としてターゲット要素のデザインを指定することもできる。

stackoverflow.com

ただ、マークアップ文書の中にデザイン目的のみの要素が混じってしまうので、個人的にはこの方法はあまり使いたくない。

【Python】動的計画法をやってみる

動的計画法アルゴリズムには前から興味があったが、なかなか身につかなかった。どのようなものか理解するために単純な問題を動的計画法で解いてみることにする。

動的計画法とは

動的計画法は、決まったアルゴリズムを指すものではなく、より広い概念である。動的計画法は、以下の2つの要素から構成される。 - 問題の分解: 問題を部分問題に分割し、その答えを使うことで全体の問題を帰納的に解く。 - 部分問題の結果の記録: 部分問題の答えを記録しておき、それを使うことで重複する処理をなくす。

部分問題への分解ができるような問題であれば、動的計画法が使える。今まで理解できていなかったのは、この定義を十分に押さえないまま、いろいろな問題の応用的な実装例ばかり見ていたせいだったのかもしれない。

動的計画法を使って問題を解く

AtCoderのABC049Cを解いてみる。この問題は過去問精選で紹介されていたが、まだ解いていなかった。

atcoder.jp

英小文字からなる文字列 Sが与えられます。 
T が空文字列である状態から始め、以下の操作を好きな回数繰り返すことで S = T とすることができるか判定してください。
・T の末尾に dream dreamer erase eraser のいずれかを追加する。

後ろから順に見ていくという簡単な解法もあるが、動的計画法の場合、Sのうち任意のn文字目までについて、問題で指定されている単語で作ることができるかを記録していく。n文字目までを作れるかと、その状態からn+1文字目以降を作っていくことができるかとは完全に独立した話なので、部分問題として分割して結果を記録することで、その結果を再利用することができる。

S = input()
dp = [0] * (len(S) + 1)
dp[0] = 1

words = ["dream", "dreamer", "erase", "eraser"]
done = 'NO'

for i in range(len(S)):
    if dp[i] == 0:
        continue
    for w in words:
        if S[i:i+len(w)] == w:
            dp[i+len(w)] = 1
    
    if dp[len(S)] == 1:
        done = 'YES'
        break

print(done)

dp[i] は、文字列Sのi文字目までを words の単語で作れるかを表し、作れる場合は1を入れる。スタート時のために dp[0] = 1 としておいた。
それぞれのループでは、i+1文字目から見て、最初の部分文字列が words の中の単語になっているかを確認する。例えば dream が作れれば、 dp[i+5] を1とする。こうすることで、 dp[i] が1でなければスキップして次のループに行くという処理ができる。
この処理ではループを2回使っているが、 words が定数種類しかないため、計算量は O(n) とみなせるだろう。

動的計画法の使い所

今回はごく単純な例だったが、動的計画法はより複雑な組み合わせ問題のときに力を発揮する。例えばナップザック問題のように、複数のパラメータを自由に変えて最適な組み合わせを求めるような問題の場合、 部分的な最適解をメモしておけば、効率的に解くことができる。パラメータを1つずつ変化させていったとき、同じパラメータの値の組み合わせが異なる場所で登場するような問題であれば、なおさら再利用の効果が高い。

Brewfile作成の際に知っておきたいHomebrewまわりの機能

自分のdotfilesをメンテナンスするにあたって、他の人のを参考にしていたら、Brewfile上で知らないコマンドがいろいろ使われていたのでまとめる。これらをうまく使えば、インストール作業の自動化の幅が広がりそうだ。

Tap

TapはHomebrewに登録されていないライブラリを、各ユーザがbrewコマンドで管理できるようにするための機能。 brew tap <user/repo> で、GitHub上にあるライブラリを登録できる(このコマンドの後にURLを指定すれば、GitHubレポジトリ以外のライブラリも登録可能)。 brew tap コマンドで、自分が登録したTapの一覧を見ることができる。
dotfiles上でよく見るのは、 後述のCask-versionsやCask-fonts、後は heroku/brew の登録あたりだろうか。 github.com

Cask

オープンソースではないソフトウェアやアプリケーションの中にも、CaskとしてHomebrewで管理されているものがある。例えば brew cask install slack とすればSlackをインストールできる。
これを使えば、App Storeからダウンロードしたり、Applicationフォルダにアイコンをドラッグしたりする手間がないので、自動化がはかどる。dotfilesを作る際はぜひ使っておきたい。
github.com

Cask-versions

Caskのバージョン管理をするためのプロジェクト。デフォルトの機能ではないので、使うには brew tap homebrew/cask-versions としておくことが必要。
これを使うと、Caskとして公開されているアプリケーションの一部について、古いバージョンやBeta版をインストールできるようになる。 github.com

Cask-fonts

Caskと同じように、fontをインストールできる。fontの設定を手動でやるのは面倒なので、使いたいfontがある場合はぜひこれを試したい。Cask-versionsと同様に brew tap homebrew/cask-fonts しておくことが必要。 github.com

mas

masはmacApp Storeを操作するcliツールである。これを使えば、Caskに登録されていないアプリケーションも自動でインストールできるようになる。XCodeをmasで入れている例もあった。
masはHomebrewのプロジェクトではないが、Homebrewと連携しており、 brew install mas をしておけば、Brewfile内で使うことができる。 github.com

この辺りを知っていれば、端末を変えたときの初期設定時間が数十分の一になりうる。今の端末が使えるうちから、設定の自動化のためにいろいろ整備しておきたい。