この記事は、ニフティグループ Advent Calendar 2023 24日目の記事です。
はじめに
こんにちは。会員システムグループの上原です。
みなさん、コーディングは好きでしょうか?私は業務ではGo, Python, TypeScript, プライベートでゴリゴリRustを書いていて毎日楽しいです。
さて、業務でコーディングしていると付きまとうのがコードレビューです。コードのどこを変えたのかGitHubが頑張って出してくれますが、見た目であったり動作はどう変わったのか?は頑張って自分で追ってやる必要があります。
テストコードを見れば仕様の変化はわかるようになっているのでは?というのはあるんですが、やっぱり自分で試しにAPIを叩いたりブラウザを動かしてみないと安心できません。
ただ、手動で動かして確かに意図した通りに動作が変わっているのか?を追いかけるのは面倒です。特にAPIが返すjsonを比較するのは大変です。
例としてSpotifyのアルバム情報を取得するAPIのレスポンス例と、その例を一部改変したレスポンスを載せています。ぱっと見何が変わったかわかりますか?一瞬でわかったあなたはすごいです。
1 |
{"album_type":"compilation","total_tracks":9,"available_markets":["CA","BR","IT"],"external_urls":{"spotify":"string"},"href":"string","id":"2up3OPMp9Tb4dAKM2erWXQ","images":[{"url":"https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228","height":300,"width":300}],"name":"string","release_date":"1981-12","release_date_precision":"year","restrictions":{"reason":"market"},"type":"album","uri":"spotify:album:2up3OPMp9Tb4dAKM2erWXQ","artists":[{"external_urls":{"spotify":"string"},"href":"string","id":"string","name":"string","type":"artist","uri":"string"}],"tracks":{"href":"https://api.spotify.com/v1/me/shows?offset=0&limit=20","limit":20,"next":"https://api.spotify.com/v1/me/shows?offset=1&limit=1","offset":0,"previous":"https://api.spotify.com/v1/me/shows?offset=1&limit=1","total":4,"items":[{"artists":[{"external_urls":{"spotify":"string"},"href":"string","id":"string","name":"string","type":"artist","uri":"string"}],"available_markets":["string"],"disc_number":0,"duration_ms":0,"explicit":false,"external_urls":{"spotify":"string"},"href":"string","id":"string","is_playable":false,"linked_from":{"external_urls":{"spotify":"string"},"href":"string","id":"string","type":"string","uri":"string"},"restrictions":{"reason":"string"},"name":"string","preview_url":"string","track_number":0,"type":"string","uri":"string","is_local":false}]},"copyrights":[{"text":"string","type":"string"}],"external_ids":{"isrc":"string","ean":"string","upc":"string"},"genres":["Egg punk","Noise rock"],"label":"string","popularity":0} |
1 |
{"name":"string","popularity":0,"release_date":"1981-12","images":[{"url":"https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228","width":300,"height":300}],"tracks":{"items":[{"is_playable":false,"duration_ms":0,"explicit":false,"is_local":false,"id":"string","external_urls":{"spotify":"string"},"track_number":0,"disc_number":0,"available_markets":["string"],"artists":[{"uri":"string","name":"string","href":"string","type":"artist","id":"string","external_urls":{"spotify":"string"}}],"preview_url":"string","restrictions":{"reason":"string"},"href":"string","linked_from":{"uri":"string","type":"string","id":"string","href":"string","external_urls":{"spotify":"string"}},"type":"string","name":"string","uri":"string"}],"limit":20,"next":"https://api.spotify.com/v1/me/shows?offset=1&limit=1","offset":0,"href":"https://api.spotify.com/v1/me/shows?offset=0&limit=20","previous":"https://api.spotify.com/v1/me/shows?offset=1&limit=1","total":4},"type":"album","uri":"spotify:album:2up3OPMp9Tb4dAKM2erWXQ","restrictions":{"reason":"market"},"id":"2up3OPMp9Tb4dAKM2erWXQ","album_type":"compilation","artists":[{"uri":"string","external_urls":{"spotify":"string"},"id":"string","href":"string","name":"string","type":"artist"}],"genres":["Egg punk","Noise rock","House Music"],"release_date_precision":"year","total_tracks":9,"label":"string","available_markets":["CA","BR","IT"],"external_ids":{"upc":"string","ean":"string","isrc":"string"},"copyrights":[{"type":"string","text":"string"}],"external_urls":{"spotify":"string"},"href":"string"} |
どうでしょうか?自分もいきなり出されたら自信がありません。
すぐにはわからない方が自分を含め大半だと思いますが、この記事を読むとすぐに違いがわかるようになります。
手始めにファイルを比べてみよう
今回はシェルを用いて違いを見つけてみます。
diffコマンドを用いて、まずはファイルの差分を見てみましょう。
AというファイルとBというファイルを比較するには以下のようにコマンドを実行すれば良いです。uオプションをつけておくとGitの差分ぽく表示されるのでさらにわかりやすくなります。
1 |
diff -u A B |
実行してみると以下のように2ファイルの差分がわかりやすく表示されます。
1 |
diff -u a.txt b.txt |
1 2 3 4 5 |
--- a.txt 2023-12-22 10:43:55 +++ b.txt 2023-12-22 10:44:01 @@ -1 +1 @@ -hello world +hello rust |
コマンドの実行結果を比べてみる
今度はファイルではなくコマンドの実行結果を比較する場合を考えてみましょう。
実行結果をファイルに書き出す機能であるリダイレクトをご存知であれば、以下のようなコマンドを実行すれば良いかなという想像がつくと思います。
1 2 3 |
コマンド1 > result_a.txt コマンド2 > result_b.txt diff -u result_a.txt result_b.txt |
まずはリダイレクトでファイルに実行結果を書き出してやり、生成したファイルをdiffコマンドで比較するという感じです。試しに使うとこんな感じです。
1 2 3 4 5 6 7 8 9 10 |
$ date > a.txt $ date > b.txt $ ls a.txt b.txt $ diff -u a.txt b.txt --- a.txt 2023-12-22 10:49:34 +++ b.txt 2023-12-22 10:49:39 @@ -1 +1 @@ -金 12 22 10:49:34 JST 2023 +金 12 22 10:49:39 JST 2023 |
はい。無事日時の差分が出てきました。dateは現在日時を出力するだけのコマンドです。
とまぁやってみましたが、コマンドの実行結果を比較するためにわざわざファイルに書き出すのは面倒ですし、なにしろ謎のファイルが増えていきますよね。
ここで使えるのがプロセス置換という方法です。
プロセス置換は、bashでコマンドの出力を一時ファイルとして扱うための機能です。これにより、複数のコマンドの結果を比較したり、変数に代入したりすることが容易になります。
プロセス置換を使用すると上のコマンドが以下のようにワンライナーに書き直せます。
1 |
diff -u <(date) <(date) |
では実行してみましょう。
1 2 |
$ diff -u <(date) <(date) (何も表示されない) |
あれ?何も表示されないですね。あ、dateは現在日時を出すので、同時に実行すると差分が何も出なくなってしまうのでした。わざと1秒ずらして実行タイミングをずらしてみましょうか。
1 2 3 4 5 6 |
diff -u <(date) <(sleep 1 && date) --- /dev/fd/11 2023-12-22 10:58:05 +++ /dev/fd/13 2023-12-22 10:58:06 @@ -1 +1 @@ -金 12 22 10:58:05 JST 2023 +金 12 22 10:58:06 JST 2023 |
デモンストレーションのためsleepコマンドを挟む必要がありましたが、無事1回で実行結果の差分を表示させることができました。
このように本来は引数としてファイル名しか受け付けないコマンドであってもプロセス置換を用いることで、コマンドの実行結果を引数として渡すことが可能になります。
diff以外でもプロセス置換は使えるのですが、コマンドの実行結果を比較したい場合は以下のようなコマンドをイディオムとして覚えておきましょう。
1 |
diff -u <(コマンド1) <(コマンド2) |
<と(の間にスペースを入れると動かなくなるので注意です。
レビューでの使用例
APIのレスポンスがどう変わったかわかりやすくする
記事冒頭で話したAPIのjsonの比較方法について考えてみます。
まずはAPIを叩くにはcurlコマンドを使用すれば良いです。
1 |
curl example.com |
そのままだと進捗状況が画面に出てくるので-Sを入れて表示されないようにします。
1 |
curl -S example.com |
APIからはjsonが一行にまとめられて返却されることが多いため、jqコマンドに通して整形しましょう。ついでにSオプションでオブジェクトのキーでソートしておくと、捗ります。
1 |
curl -S example.com | jq -S "." |
仕様変更によってリリースしたいAPI(ここでは仮にステージング環境としておく)のレスポンスをとるコマンドも作っておきましょう。
1 |
curl -S staging.example.com | jq -S "." |
さて、この二つのAPIを比較したい場合は、diffとプロセス置換を組み合わせると簡単に差分を取れます。
1 |
diff <(curl -S example.com | jq -S ".") <(curl -S staging.example.com | jq -S ".") |
引数の順番はどちらでも良いのですが、1番目に変更前、2番目に変更後の内容を入れるようにすると差分がわかりやすくなります。
先ほど出てきたjsonが返ってくるサーバーを立てて、curlしてみると以下のような結果になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
diff -u <(curl -S example.com | jq -S ".") <(curl -S staging.example.com | jq -S ".") --- /dev/fd/11 2023-12-22 13:33:26 +++ /dev/fd/13 2023-12-22 13:33:26 @@ -33,7 +33,8 @@ }, "genres": [ "Egg punk", - "Noise rock" + "Noise rock", + "House Music" ], "href": "string", "id": "2up3OPMp9Tb4dAKM2erWXQ", |
誰が見てもHouse Musicがgenresの中に追加されているとわかるので、いい感じですね!
ついでにこのコマンドをPRやissueのコメントに貼っておくと、レビュアーも自分も幸せになれると思います。
Amazon RDSのインスタンスの設定を比較する
インスタンスを複製する際にちゃんと同じ設定になっているかなど気になったことはないでしょうか?そんな時は、インスタンス同士の設定を比較して、想定した通りの設定になっているか確認してみましょう。
AWSのコンソールを横に並べて、左右見て設定が同じかどうか確認すれば良さそうですが、項目数が多く確認漏れがありそうです。
そこで今回もdiffコマンド使って比較してみます。インスタンスの設定は以下のようにAWS CLIを使えば取得することができます。
1 |
aws rds describe-db-instances --db-instance-identifier database-1 --query 'DBInstances[0]' --output json --region ap-northeast-1 |
db-instance-identifierオプションにインスタンスの識別子を指定します。outputオプションにjsonを指定するとjsonで結果が返却されます。
返ってきたjsonを比較しやすくなるように、jqコマンドで整形してやります。jqでjsonのキーをソートしないと順番がバラバラで見にくくなるので気をつけてください。以下のような感じです。
1 |
aws rds describe-db-instances --db-instance-identifier database-1 --query 'DBInstances[0]' --output json --region ap-northeast-1 | jq -S "." |
データベースの情報をとるコマンドは組み立てられたので、あとはいつも通りdiffコマンドとプロセス置換に突っ込んでやりましょう。
1 |
diff -u <(aws rds describe-db-instances --db-instance-identifier database-1 --query 'DBInstances[0]' --output json --region ap-northeast-1 | jq -S '.') <(aws rds describe-db-instances --db-instance-identifier database-2 --query 'DBInstances[0]' --output json --region ap-northeast-1 | jq -S '.') |
実行してみると以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
--- /dev/fd/63 2023-12-22 04:20:42.250700418 +0000 +++ /dev/fd/62 2023-12-22 04:20:42.250700418 +0000 @@ -4,7 +4,7 @@ "AssociatedRoles": [], "AutoMinorVersionUpgrade": true, "AvailabilityZone": "ap-northeast-1d", - "BackupRetentionPeriod": 7, + "BackupRetentionPeriod": 1, "BackupTarget": "region", "CACertificateIdentifier": "***********", "CertificateDetails": { @@ -13,9 +13,9 @@ }, "CopyTagsToSnapshot": true, "CustomerOwnedIpEnabled": false, - "DBInstanceArn": "arn:aws:rds:ap-northeast-1:************:db:database-1", + "DBInstanceArn": "arn:aws:rds:ap-northeast-1:************:db:database-2", "DBInstanceClass": "db.t3.micro", - "DBInstanceIdentifier": "database-1", + "DBInstanceIdentifier": "database-2", "DBInstanceStatus": "available", "DBParameterGroups": [ { @@ -57,28 +57,26 @@ "VpcId": "vpc-*****************" }, "DbInstancePort": 0, - "DbiResourceId": "db-**************************", + "DbiResourceId": "db-**************************", "DedicatedLogVolume": false, "DeletionProtection": false, "DomainMemberships": [], "Endpoint": { - "Address": "database-1.************.ap-northeast-1.rds.amazonaws.com", + "Address": "database-2.************.ap-northeast-1.rds.amazonaws.com", "HostedZoneId": "Z24O6O9L7SGTNB", "Port": 5432 }, "Engine": "postgres", "EngineVersion": "15.4", - "EnhancedMonitoringResourceArn": "arn:aws:logs:ap-northeast-1:************:log-group:RDSOSMetrics:log-stream:*****************************", "IAMDatabaseAuthenticationEnabled": false, - "InstanceCreateTime": "2023-12-22T02:57:01.355000+00:00", - "Iops": 3000, + "InstanceCreateTime": "2023-12-22T02:59:15.979000+00:00", "IsStorageConfigUpgradeAvailable": false, "KmsKeyId": "arn:aws:kms:ap-northeast-1:************:key/************************************", - "LatestRestorableTime": "2023-12-22T04:14:32+00:00", + "LatestRestorableTime": "2023-12-22T04:14:34+00:00", "LicenseModel": "postgresql-license", "MasterUsername": "postgres", - "MonitoringInterval": 60, - "MonitoringRoleArn": "arn:aws:iam::************:role/rds-monitoring-role", + "MaxAllocatedStorage": 1000, + "MonitoringInterval": 0, "MultiAZ": false, "NetworkType": "IPV4", "OptionGroupMemberships": [ @@ -88,14 +86,16 @@ } ], "PendingModifiedValues": {}, - "PerformanceInsightsEnabled": false, - "PreferredBackupWindow": "18:51-19:21", - "PreferredMaintenanceWindow": "thu:14:38-thu:15:08", + "PerformanceInsightsEnabled": true, + "PerformanceInsightsKMSKeyId": "arn:aws:kms:ap-northeast-1:************:key/************************************", + "PerformanceInsightsRetentionPeriod": 7, + "PreferredBackupWindow": "18:47-19:17", + "PreferredMaintenanceWindow": "sat:15:00-sat:15:30", "PubliclyAccessible": false, "ReadReplicaDBInstanceIdentifiers": [], "StorageEncrypted": true, - "StorageThroughput": 125, - "StorageType": "gp3", + "StorageThroughput": 0, + "StorageType": "gp2", "TagList": [], "VpcSecurityGroups": [ { |
コンソールを頑張って確認しなくてもコマンドの実行結果を見て差分を見ることができるようになりました!
ついでにこのコマンドをPRやissueのコメントに貼っておくと、レビュアーも自分も幸せになれると思います。(2回目)
まとめ
レビュー時、想定通りの動作になっているか細かい部分を血眼になって確認するのは大変です。
目で見て一つ一つ比較するしかないこともあるかもしれませんが、エンジニアなので可能な限り楽をできないか考えてみるのも時には大事です。
自分のためにも、またレビュアーのためにも短い時間で正しいことが確認できるような動作確認方法が何かを考えていきましょう。その際にこの記事が参考になれば幸いです。
明日は、@ike-chanさんの「AdventCalendarお疲れ様でした!ブログ」です。 お楽しみに!