gitのdiff-highlightを使い始めた
git log -p
や git diff
などで差分を見るとき、行単位での追加/削除は表示されるが、行の中のどこが変わったのかは表示してくれない。例えば行の中の一単語を書き換えただけで、しかもその行が長い場合、どこに差分があるのか目で探すのが結構大変だった。
しかし先日、 diff-highlight
という便利なモジュールが提供されていることを知り、早速導入してみた。
diff-highlightとは
git
コマンドの、行単位での差分を探す動作のポストプロセスとして実行され、同じ行の中の差分をハイライトしてくれる。
例えば、行の一部分だけ変えたときの git diff
は、今までこんな感じだった。
それがこうなる。差分がわかりやすい。
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 log
や git show
コマンドでも diff-highlight
が有効化されている。
また、普段 tig
を使っている場合、2.2.1以降であれば .tigrc
で以下の設定をすると、 tig
でも diff-highlight
が使えるようになる。
set diff-highlight = true
JJUG CCC 2019 Fallに参加しました
半年に一度のJavaカンファレンスです。個人的にこの半年は扱う技術範囲が広がり、自分の興味や課題意識も大きくなったので、今回は気になるセッションが増えました。
参加したのは以下の3つです。
入門 例外
例外処理についてのセッションです。今年の6月頃に、 System.exit
と RuntimeException
の違いを問われて答えられなかった苦い思い出があるので、体系的に例外処理を学べる良い機会でした。Javaの例外がオブジェクトとして扱われていることのメリットを初めて実感できた気がします。
時間が足りず、検査例外の話を聞けなかったので、改めて復習するつもりです。
Reliability Engineering Behind The Most Trusted Kafka Platform at LINE
業務でKafkaを触る機会が増えそうなので参加しましたが、Kafka以上に「問題の原因を根本的に解明することの重要性」の方に多くの時間が割かれています。Produce APIのレスポンスタイム遅延の原因を探るため、最終的にサーバの物理レイヤーまで潜っていった話に、そこまでやるのかと終始圧倒されっぱなしでした。
調査に多くの時間を割くことができた理由が気になったのですが、「クライアントから見えるような大きな影響がまだ出ていない状況だったから」とのことでした。その判断をするうえでも、SLOの指標を作り可視化することが大事だと気づきました。
JVMs in Containers: Best Practices
主に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も立ち上がるようになる。
また 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を立ててみる。設定ファイルはこちらの記事のものをお借りしつつ、少し変えている。
# 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
で指定してある。
また、 targetPort
は http-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
大量のファイルを一気に消したい時は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の場合だけ詳細度が下がっていることがわかる。
対応策としては、まずstyled-components内で &
を複数使い、クラスを重ねて指定する方法がある。この方法はstyled-componentsの公式ドキュメントにも載っている。
//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の中で子要素としてターゲット要素のデザインを指定することもできる。
ただ、マークアップ文書の中にデザイン目的のみの要素が混じってしまうので、個人的にはこの方法はあまり使いたくない。
【Python】動的計画法をやってみる
動的計画法のアルゴリズムには前から興味があったが、なかなか身につかなかった。どのようなものか理解するために単純な問題を動的計画法で解いてみることにする。
動的計画法とは
動的計画法は、決まったアルゴリズムを指すものではなく、より広い概念である。動的計画法は、以下の2つの要素から構成される。 - 問題の分解: 問題を部分問題に分割し、その答えを使うことで全体の問題を帰納的に解く。 - 部分問題の結果の記録: 部分問題の答えを記録しておき、それを使うことで重複する処理をなくす。
部分問題への分解ができるような問題であれば、動的計画法が使える。今まで理解できていなかったのは、この定義を十分に押さえないまま、いろいろな問題の応用的な実装例ばかり見ていたせいだったのかもしれない。
動的計画法を使って問題を解く
AtCoderのABC049Cを解いてみる。この問題は過去問精選で紹介されていたが、まだ解いていなかった。
英小文字からなる文字列 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はmacのApp Storeを操作するcliツールである。これを使えば、Caskに登録されていないアプリケーションも自動でインストールできるようになる。XCodeをmasで入れている例もあった。
masはHomebrewのプロジェクトではないが、Homebrewと連携しており、 brew install mas
をしておけば、Brewfile内で使うことができる。
github.com
この辺りを知っていれば、端末を変えたときの初期設定時間が数十分の一になりうる。今の端末が使えるうちから、設定の自動化のためにいろいろ整備しておきたい。