Zenn
🎃

[golang]プロジェクト内で参照されている特定パッケージ関数を出力してみる

に公開

この記事は、tacoms Advent Calendar 2024の4日目です!
他メンバーのAdvent Calendarはこちらからご覧ください!👇
https://qiita.com/advent-calendar/2024/tacoms

記事を書こうと思った背景

  • 弊社のサーバー実装でどんな関数が使われてるのか単純に気になったため。
  • 自分の認識してない便利関数とかあればどんどん使っていきたいので、自身のキャッチアップも兼ねてます。

実装サンプル

  • 試しに、弊社のサーバー実装で使用しているloパッケージでフィルタリングして、参照されている関数を出力してみました。
type loFunc struct {
	FuncName string
	Count    int
}

func main() {
	// プロジェクトのルートディレクトリを指定
	rootDir := "./sample"

	// プロジェクト内すべての.goファイルを収集
	var goFiles []string
	err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if filepath.Ext(path) == ".go" {
			goFiles = append(goFiles, path)
		}
		return nil
	})
	if err != nil {
		log.Fatalf("Failed to walk directory: %v", err)
	}

	// Goファイルセットを作成
	fset := token.NewFileSet()

	countByFuncName := make(map[string]int)
	// 各ファイルを解析
	for _, file := range goFiles {
		node, err := parser.ParseFile(fset, file, nil, parser.AllErrors)
		if err != nil {
			log.Printf("Failed to parse file %s: %v", file, err)
			continue
		}

		// ASTをトラバースして`lo`パッケージの関数呼び出しをリストアップ
		ast.Inspect(node, func(n ast.Node) bool {
			// 関数呼び出しを検出
			if call, ok := n.(*ast.CallExpr); ok {
				// 関数がセレクタ式 (パッケージ名.関数名) の場合
				if sel, ok := call.Fun.(*ast.SelectorExpr); ok {
					if pkgIdent, ok := sel.X.(*ast.Ident); ok && pkgIdent.Name == "lo" {
						// 関数名毎に呼び出し回数をカウント
						countByFuncName[sel.Sel.Name]++
					}
				}
			}
			return true
		})
	}
	var funcs []loFunc
	for funcName, count := range countByFuncName {
		funcs = append(funcs, loFunc{FuncName: funcName, Count: count})
	}
	// 呼び出し回数の降順でソート
	sort.Slice(funcs, func(i, j int) bool {
		return funcs[i].Count > funcs[j].Count
	})
	// 結果を出力
	for _, f := range funcs {
		fmt.Printf("%s: %d\n", f.FuncName, f.Count)
	}
}

結果

ToPtr: 1506
Map: 184
FromPtr: 97
SliceToMap: 94
GroupBy: 36
Filter: 15
Find: 11
FilterMap: 10
Contains: 10
Associate: 8
Some: 6     ←知らなかった
FlatMap: 6
MapEntries: 4 ←知らなかった
Must1: 4    ←知らなかった
Ternary: 3
Uniq: 3
MapToSlice: 3
Keys: 2
ContainsBy: 2
RangeFrom: 1 ←知らなかった
Values: 1   ←知らなかった
Assign: 1   ←知らなかった
SumBy: 1    ←知らなかった
None: 1       ←知らなかった
FromPtrOr: 1
ToSlicePtr: 1
Chunk: 1
CountBy: 1
Reduce: 1
  • 結構知らない関数あった笑
  • この中から一部ピックアップして深ぼって見ようと思います。

Some

Returns true if at least 1 element of a subset is contained into a collection. If the subset is empty Some returns false.
サブセットの少なくとも 1 つの要素がコレクションに含まれている場合は true を返します。サブセットが空の場合は false を返します。

  • これはではfor文2つ重ねて実装してたので、これを1行でかけるのはありがたい。まあ、実体は同じことしてるので、可読性の観点でしかメリットはないが。。
ok := lo.Some([]int{0, 1, 2, 3, 4, 5}, []int{0, 6})
// true

ok := lo.Some([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
// false

Must1(Must)

Wraps a function call to panics if second argument is error or false, returns the value otherwise.
2 番目の引数がエラーまたは false の場合は関数呼び出しをパニックにラップし、それ以外の場合は値を返します。

  • エラーハンドリングを無視できるっぽい。
  • テスト書いてる時、また一部の実装では引数が固定されててエラー発生が存在し得ないケースもあるため、記述量を減らすという点ではかなり有用そう。
  • エラーが発生した場合はpanicになってしまうので、注意は必要だが。。
val := lo.Must(time.Parse("2006-01-02", "2022-01-15"))
// 2022-01-15

val := lo.Must(time.Parse("2006-01-02", "bad-value"))
// panics

Assign

Merges multiple maps from left to right.
複数のマップを左から右に結合します。

  • キーが同じ場合は後続の要素に上書きされてしまうので、結構特殊なケースでしか使わなさそう。
mergedMaps := lo.Assign(
    map[string]int{"a": 1, "b": 2},
    map[string]int{"b": 3, "c": 4},
)
// map[string]int{"a": 1, "b": 3, "c": 4}

まとめ

  • プロジェクト内で参照されている特定パッケージ関数を出力してみて、実際にどういう使われ方をする関数なのか深ぼってみました。
  • Mustとかかなり有用な関数を知れたので、今後のサーバー実装で取り入れてこうと思います。
  • もちろん公式Docをちゃんと読むのが理想ですが、こういう身近な方々の使用例を参考に徐々に知識の幅を広げていく方法も、気分転換にありかな?と思いました。
tacomsテックブログ

Discussion

ログインするとコメントできます