VSCodeでGoのデバッグ
今更感はあるがVSCodeでGoのソースコードをデバッグしてみたので記録。
環境
delveインストール
VSCodeにテスト対象の$GOPATHを認識させる
- 方法は複数あるが、VSCodeのworkspaceディレクトリを自動的に$GOPATHとして認識してくれるようにするのが1番手軽そう
- settings.jsonに
"go.inferGopath": true
を追記 - stackoverflow.com
- settings.jsonに
launch.jsonを作る
- テスト対象のプロジェクトをVSCodeで開き、[構成の追加]をクリック(画像は既に構成追加済み)
- launch.jsonが作成される(作成済みの場合は追記される)
{ // IntelliSense を使用して利用可能な属性を学べます。 // 既存の属性の説明をホバーして表示します。 // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Launch", "type": "go", "request": "launch", "mode": "auto", "program": "${fileDirname}", "env": {}, "args": [] } ] }
"program": "${fileDirname}",
は開いているファイルが属するパッケージをテスト対象にする- その他の設定値に関する説明はDebugging Go code using VS Code · microsoft/vscode-go Wiki · GitHubを参照
デバッグする
- breakpointを置く
- テストコードの[debug test]をクリックする。テストメソッド単位で実行できる。
- breakpointの位置で処理が止まる。テストメソッドの中でも止められるし、テストメソッドから呼ばれた先のメソッドの中でも止められる。
- これでテストが通らなくなった場合の原因調査がスムーズに
まとめ
- VSCodeでソースコードのdebugを行う際は、launch.json等の設定ファイルへの習熟が必要な印象があったが、Goの場合はあまり考えることがない
- delveは必要だったが、それ以外はVSCodeとGo本体というシンプルな構成でデバッグ可能
Goやっていきましょう
型アサーションでインターフェイス型から本来の型を取り出す際の演算子*
環境
インターフェイス型の型アサーション
Goでは、以下のようにインターフェイスのエイリアスであるStatement
型の変数から実際の型であるExpressionStatement
を型アサーション.(T)
によって取り出すことができる。
package main import "fmt" type Statement interface { // [...] statementNode() } type ExpressionStatement struct { // [...] hoge string } func (es *ExpressionStatement) statementNode() { // [...] } func getExpressionStatement() Statement { return &ExpressionStatement{hoge: "hoge"} } func main() { exStmt := getExpressionStatement() stmt, ok := exStmt.(*ExpressionStatement) fmt.Println(stmt.hoge) fmt.Println(ok) }
演算子*
の意味
ところで、実際に型アサーションを行っているstmt, ok := exStmt.(*ExpressionStatement)
の*
はどういう意味なのか。
最初は、式の中なのでポインタ型変数の中身を参照(dereference)しているのか、と思ったがExpressionStatement
はポインタ型変数ではない。
型アサーションは型名を引数に取ることを思い出すと、構造体のエイリアス型であるExpressionStatement
のポインタ型という意味なのだろうか...? (そうかもしれない)
もしくはこの場合の演算子*
は既存の規則とは分けて考えるべきなのだろうか。
気になる。
社内でシェル芸入門について話してきた
先日弊社恒例の(ほぼ)社内勉強会であるほろよいてっくでシェル芸入門というタイトルの発表をした。こんな発表をしてはいるが自分もシェル芸初心者。
当日の発表資料はこちら。ちなみに資料ではシェル芸勉強会の過去問及び解答を多く引用させて頂いている。有用な情報を公開してくださっている@ryuichiueda氏に感謝します。
この資料でシェル芸に興味を持ってもらえたら嬉しい。
今回はスライド作成から発表準備含め合計で10時間以内に完了できた。短時間でサクっとできて嬉しい。実は元々このブログの記事にしようと思っていた内容で、既に下書きまで書いていたので調査フェーズを大幅に省略できてその分楽だった。やはり日頃からネタをストックしておくのは大事だなあと思う今日この頃だった。
コードレビューを受ける前に確認しておきたいチェックリストを整理してみた
レビュアーの負荷軽減とレビュー時間の短縮を目的として 、コードレビューを依頼する前に確認しておくべきチェックリストを整理してみた*1
チェックリストは特定の言語・フレームワーク、開発内容(バックエンド・フロントエンド)に依存しない内容になるように心がけてまとめた。
以下チェックリスト
- ☑ 仕様を満たしているか
- ☑ 仕様が伝わるか
- ☑ 影響範囲を記載しているか
- ☑ 不要なコードが残っていないか
- ☑ 明らかなアンチパターンが含まれていないか
- ☑ コードのフォーマットが崩れていないか
- ☑ ユニットテストが全てパスするか
- ☑ 適切なコメントが書けているか
☑ 仕様を満たしているか
- 予め整理されていた仕様通りに実装されているか
- 仕様が抜けている・間違っているとコードレビューをしても仕方がない
- 仕様の不明な箇所を想像で実装していないか
- 非機能要件(実行環境の差異、性能、大量データ等)を考慮・想定しているか
☑ 仕様が伝わるか
- 実装した仕様は明文化されているか
- レビュアーが仕様を理解できないと形式的なコードレビューに終始してしまう恐れがある。必ずドキュメント化するべきというわけではなく、仕様が明らかでレビュアーと互いに合意が取れているのであればチケットで補足する程度でも大丈夫と思う
☑ 影響範囲を記載しているか
- 機能の追加、不具合修正等既存のコードに手を加えている場合はどこに影響するのかを記載すると、レビュアーの負荷を下げられる
- 影響範囲が不明なままコードを修正するのは止めよう
☑ 不要なコードが残っていないか
- 検証用のログ出力や処理、コメントアウトされたまま最終的には使わなかったコード等が残っていないか
- 後から人力で検出するのは辛いので、都度”TODO:後で消す”などのコメントを付与してIDEで管理するのが良い(VSCodeなら Todo Tree - Visual Studio Marketplace)
☑ 明らかなアンチパターンが含まれていないか
- 主にパフォーマンスやセキュリティの観点
☑ コードのフォーマットが崩れていないか
- インデントの崩れ、横幅の超過、不要な空白や改行はないか
- 人力で修正するのも指摘するのも辛いので自動フォーマットの仕組みを導入するべき(prettier等)
☑ ユニットテストが全てパスするか
- 新規に追加したユニットテスト、既存のユニットテストを含めて全てパスするか
- 開発途中までは通っていたはずなのにな...? ということが意外とある
- 全て実行するにはユニットテストの数が多すぎる場合は、開発箇所と関連する部分を選択する。(影響範囲を特定しきれないという場合もあるかもしれないが、それはそもそもコードを書く前に確認して明らかにするべきこと)
☑ 適切なコメントが書けているか
コメントは後からコードを読む・書く人のために書くものだが、良質なコメントはコードレビューにおけるレビューアーとのコミュニケーションを減らし、レビュー時間を短縮することにも繋がる。
クラス
- 何のために、何をするクラスなのか
- 文章化してみると実はクラス設計自体に問題があるということも少なくない
メソッド
園田:一般論として最初に検討するべき候補はあります。「(1)事前条件」「(2)事後条件」「(3)不変条件」「(4)入力の意味」「(5)出力の意味」「(6)副作用」「(7)計算量」です。要するに、利用する側が知っておくべきだけれど、コードの中身を見なければ容易には分からないものです。
Rubyコミッター・Yuguiに学ぶ、コードに書くべき「適切なコメント」と「適切な場所」 - エンジニアHub|若手Webエンジニアのキャリアを考える!より引用。
事前条件、事後条件、不変条件という言葉に馴染みがない人はオブジェクト指向入門 第2版 原則・コンセプトの第11,12章を読み契約による設計の概念に触れてみよう
最近の自分は所謂Javadoc(関数の説明、引数・返値の内容)+事前条件、事後条件、(必要なら不変条件)という形に帰着している
テストメソッド
- どういったケースにおける何を検証するテストなのか
- 正常系・異常系のどちらを確認しているのか
制御構文
- 特にif文の条件式の意図が伝わりにくいことが多い。条件式を自然言語に書き直すわけではなく、なぜその条件式が必要なのかを書く
- しかし基本的には、伝わりにくいコードをコメントで補うのではなくコード自体を見直すべき。式が長い場合には説明変数に代入できないか、式が簡潔だが意図が伝わらない場合は要約変数を利用できないか?(リーダブルコードの8章を参照)
その他
- 問題があることを認識しつつも時間がない、今回の修正目的の範囲を超える等の理由で修正しない場合はFIXME、WARNING等のコメントを付与
- 部分的なリファクタリングで、どうしても過去の負債を引き継がないといけない場合はその旨を書く
- コメント内容自体に誤りがないか(実装と合っているか)
*1:コードレビューの定義や仕方はチームや会社によって異なるとは思うが、このチェックリストはそれなりに普遍的な、つまりコードレビューで指摘されずにmasterブランチに含まれると問題があるような内容がほとんどなんじゃないかと思う。
CSVファイル差分比較マニュアル
環境
やりたいこと
以下、CSVファイルの差分比較を行う手順と注意事項を書いていく。
比較対象のCSVファイルを整形する
- CSVファイルを比較可能な形に揃える作業
- 自分の経験上、業務でCSVファイルの比較を行う場合は何らかの前処理(余分な行・列の削除、ソート)が必要なことが多い
- シェルスクリプトで整形する
- 正直Excelでもできるが、Excelで編集した途端に日付、ダブルクォート、真偽値(true, false)が勝手に大文字になる、先頭の0が消滅する等何らかの災いが発生するので使わないほうが賢明と思う
ソート
$ sort
- 文字コード、辞書、数値を基準に昇順・降順のソートが可能
- ソート基準対象の列も指定できる
- 詳細は Man page of SORT
- sortコマンドについて詳しくまとめました 【Linuxコマンド集】
- ただし、DBから取得したデータ等でユニークキーが存在する場合は後述のcsvdiffを使えばソートは不要そう
補足
- カラムが多すぎてソートに指定したい列が何番目か分からなかったりする場合のワンライナー例
cat hoge.csv | head -n 1 | sed -e 's/,/\n/g' | nl 1 "都道府県コード" 2 "都道府県名" 3 "元号" 4 "和暦(年)"
文字コード変換
- Shift_JISだとターミナル上で日本語が文字化けする
# 文字コード判定 $ nkf -g hoge.csv Shift_JIS # UTF-8に変換(上書き) $ nkf --overwrite -w hoge.csv
実際に比較する
git diff
- こういうCSVファイルの場合
// test.csv "1","2","3","4","5" "6","7","8","9","10"
// test2.csv "1","2","3","4","5" "6","7","10","9","11"
- 比較する(2行目の3列目と5列目に差異)
$ git diff --word-diff-regex="[^[:space:],]+" test.csv test2.csv diff --git a/test.csv b/test2.csv index bbe528b..e69d468 100644 --- a/test.csv +++ b/test2.csv @@ -1,2 +1,2 @@ "1","2","3","4","5" "6","7"[-"8","9"-],"10",{+"9","11"+}
- 確かに差分比較できているが、上記のようなケースだと2行目3・5列目に差異があることが明確に視認できない(気がするのは自分だけ?)
csvdiff
- もっと良さげなのを探した
- 3列目と5列目に差異が発生していることが明確に分かる
csvdiff test.csv test2.csv -o word-diff # Additions (0) # Modifications (1) 6,7,[-8-]{+10+},9,[-10-]{+11+}
- また、このようにユニークキーを持つCSVファイルの場合は自動的にソート(比較行を選択)してくれる
// test3.csv aaa, 1, 2, 3, 4, 5 bbb, 6, 7, 8, 9, 10
// test4.csv bbb, 6, 7, 10, 9, 11 aaa, 1, 2, 3, 4, 5
$ csvdiff test.csv test2.csv -o word-diff -p 1 # Additions (0) # Modifications (1) bbb, 6, 7,[- 8-]{+ 10+}, 9,[- 10-]{+ 11+}
- 比較したい列番号が予め分かっている場合は、その列だけで比較判定できる
$ csvdiff test.csv test2.csv -o word-diff -p 0 --columns 2 # Additions (0) # Modifications (0)
まとめ
- csvdiff便利そう
Map型の変数の命名について考えた
Map型の変数
ふと、Map型の変数の名前の命名って何がベストなんだろうと気になったのでパターンをまとめてみた。
ハンガリアン記法パターン
とりあえず変数名の末尾にMapをつけておくパターン。 例えば、社員コードと社員名の対応付けを格納するMapの場合はこんな感じだろうか。
var empCodeMap = new Map(); empCodeMap.set('123', '太郎'); empCodeMap.get('123');
型宣言が不要な言語の場合は命名の選択肢としてはアリかもしれない。 そうでないならあまり選択するメリットがないと思う。
KeyToValueパターン
キーと対応する値を変数名に含めるパターン。 シンプルで情報量が多いのが利点。私はいつもこの命名パターンを利用してMap型の変数を宣言する。
var empCodeToName = new Map(); empCodeToName.set('123', '太郎'); empCodeToName.get('123');
valueByKeyパターン
値を変数名の先頭に持ってきてキーを後に続けるパターン。 KeyToValueパターンと情報量は変わらないので好みの問題?
var nameByEmpCode = new Map(); nameByEmpCode.set('123', '太郎'); nameByEmpCode.get('123');
valueOfパターン
言語の構文によっては前置詞が自然に繋がるので良いのかもしれない。 例えばJavaScriptやJavaだと不自然な上に情報量が減るので悪い命名だと思う。
var empCodeOf = new Map(); empCodeOf.set('123', '太郎'); empCodeOf.get('123');
参考
「プロを目指す人のためのRuby入門」を読んでRubyに入門した感想
プロを目指す人のためのRuby入門
自分のスキル
- エンジニア業務歴2年ぐらい
- いわゆるスクリプト言語、コンパイル言語は両方経験ある(主にJavaScript, Apex(Java)等)
- プログラミング自体は学生時代含めて6年前後はやってる
- Rubyほぼ知らない
感想
本の感想というよりはRubyを初めて学んで感じたことをジャンル別に整理してみた。
驚き
(少なくとも自分は)他の言語で見たことがない規則や構文
- 変数を宣言するだけの構文はない
- 末尾が?のメソッド名は返り値が真偽値、末尾が!は副作用ありを示す慣習
- メソッド内で最後に評価された式が戻り値になる
- メソッド宣言時や呼び出し時に引数のカッコを省略可能
- ifの反対の意味を持つunless
- シンボルの概念(JavaのEnumと同じな気もする)
- ifの条件式でローカル変数に代入
- privateメソッドはレシーバーを指定できないという定義
- aliasキーワード(便利に見えるけど混乱するだけな気もする)
- クラスにアクセス修飾子が使えない
JavaScriptにも欲しい
JavaScriptとは違う
- メソッド呼び出し時の引数の過不足はエラー
- ブロックの概念(クロージャ)
- 範囲オブジェクトがある
JavaScriptと似ている
- 後付で何でも修正できる
忘れそう・難しい
- 引数の最後がハッシュならハッシュリテラルの{}を省略可能
- メソッド宣言時の引数の種類と並び順
- Proc.newとラムダの違い
- 二重コロンでモジュール内のクラス、クラス内の定数参照
例外クラスについて
(プロを目指す人のためのRuby入門 図9-3より引用)
総括
- (慣れの問題とは思うが)Rubyは省略記法や構文が独特かつ自由な印象があってすぐに忘れそう
- その分他の言語と比べると、表現したいことに対してコードの量は少なく済むので慣れている人は書きやすいし読みやすいのかも
- 静的型付がないので、コードの品質を保てないまま規模が大きくなると非常に辛そう(JavaScriptと同様な感じになる)