go/analysisのSuggestedFixでコードを修正する

Goの既存コードを修正するツールを作る時、

  • 既存コードをどう書き換えて
  • 出力して
  • テストするか

を考えなければいけないのが少し面倒だと思っていました。
が、golang.org/x/tools/go/analysisSuggestedFixを使えばすごく簡単にできてしまいます。

golang.org/x/tools/go/analysisstaticcheckgolangci-lintなどの静的解析ツールでよく使われているパッケージです。

例えば以下のような、関数の引数にcontext.Contextがあるかどうかチェックするツールがあったとして、

func run(pass *analysis.Pass) (interface{}, error) {
    inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

    nodeFilter := []ast.Node{
        (*ast.FuncDecl)(nil),
    }

    inspect.Preorder(nodeFilter, func(n ast.Node) {
        decl := n.(*ast.FuncDecl)
        if decl.Type.Params.NumFields() > 0 {
            // NOTE: 第1引数のみを文字列でチェックしているので厳密ではない
            if types.ExprString(decl.Type.Params.List[0].Type) == "context.Context" {
                return
            }
        }

        pass.Reportf(decl.Pos(), "missing ctx in parameter")
    })

    return nil, nil
}

これを、もしチェックに引っ掛かったら引数にcontext.Contextを追加できるように変更してみます。

まずはpass.Reportfpass.Reportに変更し、直接Diagnosticを渡せる形にします。

pass.Report(analysis.Diagnostic{
    Pos:     decl.Pos(),
    Message: "missing context in parameter",
})

そしてSuggestedFixesとしてコードを変更する場所(PosからEnd)と書き換え後のコード(NewText)を渡します。

pass.Report(analysis.Diagnostic{
    Pos:     decl.Pos(),
    Message: "missing context in parameter",
    SuggestedFixes: []analysis.SuggestedFix{{
        Message: "add ctx to parameter",
        TextEdits: []analysis.TextEdit{{
            Pos:     decl.Pos(),
            End:     decl.Type.Params.Closing + 1,
            NewText: b,
        }},
    }},
})

書き換え後のコードは標準パッケージのformat.Nodeを使って作ります。

func newText(pass *analysis.Pass, decl *ast.FuncDecl) ([]byte, error) {
    // Godoc、戻り値、関数の中身は使わずにコードを整形する
    f := &ast.FuncDecl{
        Recv: decl.Recv,
        Name: decl.Name,
        Type: &ast.FuncType{
            Params: &ast.FieldList{
                List: append([]*ast.Field{{
                    Names: []*ast.Ident{{Name: "ctx"}},
                    Type: &ast.SelectorExpr{
                        X:   &ast.Ident{Name: "context"},
                        Sel: &ast.Ident{Name: "Context"},
                    },
                }}, decl.Type.Params.List...),
            },
        },
    }

    var buf bytes.Buffer
    if err := format.Node(&buf, pass.Fset, f); err != nil {
        return nil, err
    }
    return buf.Bytes(), nil
}

この書き換えを実際に適用するにはコマンドラインツールとして実行する時に-fixフラグを付けるようにすればOKです。
なお、-fixフラグはunitcheckerだと渡せないため、main.gosinglecheckermulticheckerを使う必要があります。

もしくは、goplsAnalyzerとして組み込むことでエディタと連携して使うことも可能です。
多少作り込みが甘くても、リファクタリングする時だけ以下に追加し、go installして使ってみても良いかもしれません。 https://github.com/golang/tools/blob/gopls/v0.6.4/internal/lsp/source/options.go#L1108-L1150

vim + vim-lspは該当箇所で:LspCodeActionを実行すると呼び出せます。

f:id:daisuzu:20210128120803g:plain
vim-lspのLspCodeAction

テストについてはanalysistest.Runanalysistest.RunWithSuggestedFixesに変更すればgoldenファイルと比較してくれるようになります。

go-cmpでmap[string]interface{}のJSONを比較する

GoでJSONを扱う際、型を定義せずに map[string]interface{} を使いたくなることがあります。

var (
    a = map[string]interface{}{
        "data": map[string]interface{}{
            "value": int64(1),
        },
    }
    b = map[string]interface{}{
        "data": map[string]interface{}{
            "value": float64(1),
        },
    }
)

ちょっとした用途であれば特に問題ないかもしれませんが、テストで使おうとするとたまに数値のフィールドがfloat64とint64で比較できずに困ってしまいます。
(goldenファイルを読み込んだ場合など)

func TestReflect(t *testing.T) {
    if !reflect.DeepEqual(a, b) {
        t.Errorf("%v != %v", a, b)
    }
}

こちらはint64が含まれている方をjson.Marshalし、再度json.Unmarshalすることでfloat64にすることで回避できます。

func TestReflect2(t *testing.T) {
    tmp, err := json.Marshal(a)
    if err != nil {
        t.Fatal(err)
    }
    var got map[string]interface{}
    if err := json.Unmarshal(tmp, &got); err != nil {
        t.Fatal(err)
    }
    if !reflect.DeepEqual(got, b) {
        t.Errorf("%v != %v", got, b)
    }
}

ただ、なんだか無駄な変換をしているようでモヤモヤします。

モヤモヤするのであればきちんと型を定義するべきだとは思いますが、どうしてもstructを作りたくないことがあるかもしれません。
そんな時はgithub.com/google/go-cmp/cmpFilterValuesを使用すると数値をfloat64として比較できます。

func TestCmpWithOpt(t *testing.T) {
    opt := cmp.FilterValues(func(x, y interface{}) bool {
        return isNumber(x) && isNumber(y)
    }, cmp.Comparer(func(x, y interface{}) bool {
        return cmp.Equal(toFloat64(x), toFloat64(y))
    }))
    if !cmp.Equal(a, b, opt) {
        t.Errorf("%v != %v", a, b)
    }
}

func isNumber(v interface{}) bool {
    k := reflect.ValueOf(v).Kind()
    return k == reflect.Int64 || k == reflect.Float64
}

func toFloat64(v interface{}) float64 {
    rv := reflect.ValueOf(v)
    if rv.Kind() == reflect.Int64 {
        return float64(rv.Int())
    }
    return rv.Float()
}

FilterValuesの第1引数には第2引数(opt)を評価する条件となる関数を指定します。
mapのフィールドは全てinterface{}なのでxとyの型はinterface{}にする必要があります。

第2引数では実際に比較する関数を指定します。
このタイミングで数値をfloat64に変換して比較します。
なお、cmp.Comparerのみだとcannot use an unfiltered optionでpanicしてしまいます。

全体のコードはこちらです。

編集を加速するVimのquickfix機能

この記事はVim Advent Calendar 2020の3日目の記事です。
昨日は@mira010さんのvim pluginsをインストールしてみましょうでした。

みなさんquickfixを使っていますか?
Vimのquickfix機能はgrepやmakeなどの結果を保持する専用のバッファと、それを扱うための各種コマンドからなります。
IDEには当たり前のようにあるような機能ですが、Vimの場合は他の機能と組み合わせることで編集操作を格段に効率化できます。

:grep:makeも、

  1. 外部コマンドを指定した引数で実行し、
  2. ファイル名や行番号、メッセージなどの出力を解析し、
  3. ジャンプのために使えるリストを作ってくれる

のは共通です。
このリストはquickfixリストと呼ばれるもので、:copenで専用のウィンドウが開きます。
そして<Enter>キーやダブルクリックで該当行にジャンプします。

ただデフォルトのgrepコマンドはまだしも、makeコマンドは滅多に使わないという人もいるかもしれません。
その際はgrepprgmakeprgオプションで実行する外部コマンドを任意のコマンドに変更できます。
(出力される結果が解析できない形式の場合はerrorformatなども変更する必要があります。)

grepコマンドはgit grep

" スペースはエスケープが必要
set grepprg=git\ grep\ -n\ --no-color

makeコマンドはlinterやタスクランナーなどにするとグッと使いやすくなります。

" 使用例
" :make ./...
" :make --disable-all -E staticcheck
set makeprg=golangci-lint\ run

これらの設定はvimrcに書いておいたり、簡単に切り替えられるコマンドやマッピングを用意しておいても良いでしょう。

しかし、quickfixリストは同時に複数の結果を表示することができません。
そのため、複数のquickfixリストを扱うには:colder:cnewer:chistoryを使って履歴を行き来する必要があります。
もしくはlocationリストを使います。

locationリストとはウィンドウローカルなquickfixリストのことで、コマンドのプレフィックス

だけで、quickfixリストと同じように使えます。

そのため、別のウィンドウやタブページで個別にlocationリストを開くことで複数の結果を表示できます。

さて、ここまで紹介した機能だと便利なジャンプリストでしかありません。
quickfixリストを使ってさらに効率的な編集をするには、:cnextとマクロを組み合わせて使います。

例えばgrepで絞り込んだ行の特定の文字列を置換したければ、以下の操作を(qqなどで)マクロに記録し、

:s/Before/After/
:w
:cnext

大きな数を指定して(100@q1000@qで)実行すると、quickfixリストの最後まで自動的に繰り返してくれます。
リストが指定した数より少なければそこでマクロが止まってくれるので重複実行は気にしなくて大丈夫です。

この時、さらにquickfixリストを絞り込みたくなることがあるかもしれません。 :grepの場合は正規表現を工夫しても良いですが、:packadd cfilterで使えるようになる:Cfilterでquickfixリストを絞り込んでしまうのがとても楽です。

それでも絞り込むのが難しい場合、いったん別のバッファにコピーして編集し、:cbuffer:cgetbufferでquickfixリストを読み込み直しても構いません。
(quickfixリストを:setlocal modifiableして書き換えるのはちょっと乱暴なので非推奨です。)

また、quickfixリスト自体をファイルとして保存しておき、:cfile:cgetfileで読み込み直すことも可能です。
そうするとジャンプ可能なTODOリストとしても使えるので、リストが巨大だったら少しずつ進めたり、他のVimmerと作業を分担する、なんてこともできるかもしれません。

ということで、ぜひquickfix機能を活用してみてください。

本当はより実践的な例として、最近やったことを具体的なコマンド付きで紹介できれば良かったんですが、ある事情でそのヒストリーをロストして再現環境もなく...
覚えている範囲でフワッと紹介して終わります。

目的はterraformerで生成したmonitoring_alert_policy.tfファイルのdocumentationに、フィルター付きでCloud LoggingのWeb画面に飛べるリンクを追加することでした。
そのフィルターの内容はlogging_metric.tfファイルに定義されているものを使います。

  • monitoring_alert_policy.tf(のサンプル)
resource "google_monitoring_alert_policy" "alert_policy_error" {
  display_name = "My Alert Policy(Error)"
  combiner     = "OR"
  conditions {
    display_name = "test condition"
    condition_threshold {
      filter     = "resource.type=gae_app AND metric.type=logging.googleapis.com/user/my-error-metric"
      duration   = "60s"
      comparison = "COMPARISON_GT"
      aggregations {
        alignment_period   = "60s"
        per_series_aligner = "ALIGN_RATE"
      }
      threshold_value = 0.1
      trigger {
        count = 1
      }
    }
  }

  documentation = {
    mime_type = "text/markdown"
    # TODO: ここに↓の形式でリンクを入れたい
    # - [NAME](https://console.cloud.google.com/logs/query?project=PROJECT_ID&query=FILTER)
    content = ""
  }
}
  • logging_metric.tf(のサンプル)
resource "google_logging_metric" "logging_metric_error" {
  name   = "my-error-metric"
  filter = "resource.type=gae_app AND severity>=ERROR"
  metric_descriptor {
    metric_kind = "DELTA"
    value_type  = "INT64"
  }
}

手順:

  1. :grepする
  2. :copenして、不要な行があれば除外する
  3. マクロを記録開始して、
    1. getline()substitute()で検索するmetricの名前を抽出する
    2. logging_metric.tfのバッファに移動する
    3. search()getline()substitute()を使ってfilterを取得する
    4. 元バッファに戻る
    5. documentationのcontentがある行に移動する
    6. リンクを生成して追加する
      • conditionsが複数ある場合はそれぞれのリンクを追加する
      • フィルターはbase64化してクエリパラメータにする必要がある
    7. :wで保存する
    8. :cnextする
  4. マクロの記録を終了する
  5. 1000回ほど繰り返す

実は初めの数回は関数などを使用せず、ノーマルモードコマンドで普通に編集をしていました。
ただ同じような操作を繰り返していることは薄々感じていたのと、残りの件数を見て即マクロに切り替えたという経緯があります。
そのまま続けていたら数時間はかかっていたと思いますが、ほぼ一瞬で終わらせることができました。

明日は@kaneshinさんです。

GoでDBのテストにgithub.com/cockroachdb/copyistを使う

久しぶりにMySQLを使ったシステムを触ることになったものの、現状のテストが遅すぎたので高速化に取り組むことにした。

遅い原因としては、

  • テストでDBを使うパッケージが多いので接続時間がそれなりになってしまう
  • テストケースごとにTRUNCATE→INSERTでデータを入れ直している

といったところ。
なので今回はDBにアクセスしないでテストができるようになるcopyistを試してみようと思う。

github.com

仕組みとしては実行されたSQLを記録しておき、以降はその時のデータを使うという

などと似たようなものとなる。

README.mdにはMySQLは非対応と書いてあったが、普通に使うことができた。
(もしかしたら一部正しく記録/再生できないクエリがあるのかもしれない)

MySQL版のサンプルコードはこちら。

mysql_test.go

package example

import (
    "database/sql"
    "log"
    "testing"

    "github.com/cockroachdb/copyist"
    _ "github.com/go-sql-driver/mysql"
)

// GetName をテストする。
func GetName(db *sql.DB, id int64) (string, error) {
    var name string
    err := db.QueryRow("SELECT name FROM `user` WHERE id=?", id).Scan(&name)
    return name, err
}

// resetDBで使うためにグローバル変数にしておく。
var db *sql.DB

// 記録時にcopyist.Open()の中で実行される。
func resetDB() {
    db.Exec("DROP TABLE `user`")
    db.Exec("CREATE TABLE `user` (`id` INT(11) unsigned NOT NULL AUTO_INCREMENT, `name` VARCHAR(20) NOT NULL, PRIMARY KEY (`id`))")
    db.Exec("INSERT INTO `user` (id, name) VALUES (?, ?)", 1, "Andy")
}

func TestMain(m *testing.M) {
    // ドライバ名は"copyist_mysql"になる。
    copyist.Register("mysql", resetDB)

    // resetDBとテストで使うdbを作る。
    var err error
    db, err = sql.Open("copyist_mysql", "admin:pass@/copyist")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    m.Run()
}

func TestGetName(t *testing.T) {
    defer copyist.Open(t).Close()

    // found
    name, err := GetName(db, 1)
    if err != nil {
        t.Fatal(err)
    }
    if name != "Andy" {
        t.Error("failed test")
    }

    // not found
    if _, err := GetName(db, 100); err != sql.ErrNoRows {
        t.Error(err)
    }
}

// subtest版
func TestGetName_subtest(t *testing.T) {
    t.Run("found", func(t *testing.T) {
        defer copyist.Open(t).Close()

        name, err := GetName(db, 1)
        if err != nil {
            t.Fatal(err)
        }
        if name != "Andy" {
            t.Error("failed test")
        }
    })

    t.Run("not found", func(t *testing.T) {
        defer copyist.Open(t).Close()

        if _, err := GetName(db, 100); err != sql.ErrNoRows {
            t.Error(err)
        }
    })
}

1回目は以下のように -record をつけてテストを実行する。

$ go test -v -record
=== RUN   TestGetName
--- PASS: TestGetName (0.09s)
=== RUN   TestGetName_subtest
=== RUN   TestGetName_subtest/found
=== RUN   TestGetName_subtest/not_found
--- PASS: TestGetName_subtest (0.12s)
    --- PASS: TestGetName_subtest/found (0.06s)
    --- PASS: TestGetName_subtest/not_found (0.06s)
PASS

そうするとtestdataディレクトに以下のようなmysql_test.copyistが生成される。

1=DriverOpen 1:nil
2=ConnPrepare   2:"SELECT name FROM `user` WHERE id=?"  1:nil
3=StmtNumInput  3:1
4=StmtQuery 1:nil
5=RowsColumns   9:["name"]
6=RowsNext  11:[10:QW5keQ]  1:nil
7=RowsNext  11:[]   7:EOF

"TestGetName"=1,2,3,4,5,6,2,3,4,5,7
"TestGetName_subtest/found"=1,2,3,4,5,6
"TestGetName_subtest/not_found"=1,2,3,4,5,7

この状態で -record をつけずに実行すると↑のデータが使われる。

$ go test -v
=== RUN   TestGetName
--- PASS: TestGetName (0.00s)
=== RUN   TestGetName_subtest
=== RUN   TestGetName_subtest/found
=== RUN   TestGetName_subtest/not_found
--- PASS: TestGetName_subtest (0.00s)
    --- PASS: TestGetName_subtest/found (0.00s)
    --- PASS: TestGetName_subtest/not_found (0.00s)
PASS

なお、記録する前に -record 無しで実行するとpanicになる。

$ go test -v
=== RUN   TestGetName
--- FAIL: TestGetName (0.00s)
panic: no recording exists with this name: TestGetName [recovered]
        panic: no recording exists with this name: TestGetName

また、以下のようにクエリを変えて、

--- a/mysql_test.go
+++ b/mysql_test.go
@@ -12,7 +12,7 @@ import (
 // GetName をテストする。
 func GetName(db *sql.DB, id int64) (string, error) {
        var name string
-       err := db.QueryRow("SELECT name FROM `user` WHERE id=?", id).Scan(&name)
+       err := db.QueryRow("SELECT name FROM `user` WHERE id=? AND TRUE", id).Scan(&name)
        return name, err
 }

記録し直さずにそのまま実行してもpanicになる。

$ go test -v
=== RUN   TestGetName
--- FAIL: TestGetName (0.00s)
panic: mismatched argument to ConnPrepare, expected SELECT name FROM `user` WHERE id=? AND TRUE, got SELECT name FROM `user` WHERE id=? - regenerate recording [recovered]
        panic: mismatched argument to ConnPrepare, expected SELECT name FROM `user` WHERE id=? AND TRUE, got SELECT name FROM `user` WHERE id=? - regenerate recording

ただし、プレースホルダーの値が変わっても返ってくるレコードは変わらないのでその点は注意が必要。
例えば以下のようにidを逆にしてもテストはPassしてしまう。

--- a/mysql_test.go
+++ b/mysql_test.go
@@ -45,7 +45,7 @@ func TestGetName(t *testing.T) {
        defer copyist.Open(t).Close()

        // found
-       name, err := GetName(db, 1)
+       name, err := GetName(db, 100)
        if err != nil {
                t.Fatal(err)
        }
@@ -54,7 +54,7 @@ func TestGetName(t *testing.T) {
        }

        // not found
-       if _, err := GetName(db, 100); err != sql.ErrNoRows {
+       if _, err := GetName(db, 1); err != sql.ErrNoRows {
                t.Error(err)
        }
 }
@@ -63,7 +63,7 @@ func TestGetName_subtest(t *testing.T) {
        t.Run("found", func(t *testing.T) {
                defer copyist.Open(t).Close()

-               name, err := GetName(db, 1)
+               name, err := GetName(db, 100)
                if err != nil {
                        t.Fatal(err)
                }
@@ -75,7 +75,7 @@ func TestGetName_subtest(t *testing.T) {
        t.Run("not found", func(t *testing.T) {
                defer copyist.Open(t).Close()

-               if _, err := GetName(db, 100); err != sql.ErrNoRows {
+               if _, err := GetName(db, 1); err != sql.ErrNoRows {
                        t.Error(err)
                }
        })

細かい部分はまだ何とも言えないが、とりあえず使えそうだし高速化にも期待ができそう。

さて、あとはこれをどうやって既存のテストに組み込んでいくか。
既存のテストではxormが使われているが、copyistに対応していないので少し特殊な初期化をしないといけないようだ。

engine, err := xorm.NewEngine("mysql", "")
// エラーハンドリングやengineの設定など
// ...

// DBをsql.Open("copyist_mysql", ...)したものと差し替える
engine.DB().DB = db

これを何とかするのが一番大変なのかもしれない...

ISUCON10に初参戦してみた

ゴリラさんと出てみようと話していたものの、申し込みのタイミングを逃してオワタと思っていたら@inductorさんに拾ってもらい、カンガルーと犬とゴリラとしてなんとかISUCON10に参加することができました。

当日は開始ちょっと前*1にDiscordとGoogleドキュメントでコミュニケーションすることを決め、 開始直後は

  • 環境を整備する
  • アプリを見る
  • ドキュメントを見る

といった感じで役割分担。

30分くらい経って、これからはベンチマークを走らせながらどこから手を付けるか考えようと思っていましたがトラブルのためなかなか実行できず、New Relicの設定をしたあとはしばらくWebアプリをポチポチしたりホストの中をウロウロしたりしていました。

ベンチマークが走ってからは、

  • 検索系
    • インデックス
    • DB分割
      • 最終的にはApp1台 + DB2台に
  • nazotte
    • LIMITでループを抜ける
    • それ以上のチューニングは結局できず...
  • low_priced
    • 静的に返したり、キャッシュしたり
  • botからのアクセス
    • nginxで弾く

をなんとかすることに。
これでスコアが480→805になったのがだいたい19:00で、そこから都合により20:20くらいまで離席...

戻ってきたタイミングで検索系がさらに最適化されてスコアは1280まで伸びましたが、最終的には1244で競技終了の21:00になりました。

以前に参加した社内ISUCONでは個人でもチームでもスコアアップに繋がるようなことがほとんどできなかったので*2、それに比べれば多少はできるようになったかもしれませんが、まだまだ力不足*3なことを痛感しました。。。

*1:12:20開始になったので11:50くらい

*2:初期スコアからほぼ変わらなかったはず

*3:特にMySQL、そしてインフラは完全にinductorさん任せ!

goplsのSymbolMatcherとSymbolStyleオプション

以下の記事で、workspace/symbolのオプションは補完と同じmatcherと紹介したが、現在のv0.4.4ではsymbolMatchersymbolStyleになっている。 daisuzu.hatenablog.com

まずはv0.4.1matchersymbolMatcherに変わり、それまでと同様、

  • fuzzy
  • caseSensitive
  • caseInsensitive(デフォルト)

の3つが補完と独立して設定できるようになった。

その後、v0.4.4symbolStyleが追加され、

  • package(デフォルト)
  • full
  • dynamic

を指定できるようになった。

packageの場合は今までと同様、パッケージ名をクエリに含める際にはコードで使う時のようにcontext.Contextといった形式で検索する。

新たに追加されたfullではgolang.org/x/net/context.Contextのようにすることで同じパッケージ名の別モジュールを区別することが可能になった。

dynamicはpackage→fullの順番でマッチさせるため、パッケージ名を含んだクエリはpackageと同じ結果になり、インポートパスから指定したクエリはfullと同じ結果になる。

Goでprotobufの定義をimportせずにフィールドの値を使う

以下のようなコードでreqにあるフィールドを使いたい場合、

opt := grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    // ここでreqから特定のフィールド値を取得したい
    return nil
})

型が定義されているパッケージをインポートして、

if v, ok := req.(*pb.Req); ok {
    // v.Bodyでフィールドにアクセスできる
}

のように変換することになる。

この時、インポートしたくない or インポートできないのであれば、protocが生成する定義にはGetterも同時に生成されるため、対象フィールドのGetterがあるinterfaceで型アサーションすると手軽にできる。

if v, ok := req.(interface{ GetBody() []byte }); ok {
    // v.GetBody()でフィールドの値を取得できる
}

あまり使い所はないかもしれないが、google.golang.org/appengineは定義がinternal配下にあるため、例えばテストでWithAPICallFuncを使う際など、以下のようにしてtaskqueue.Addのリクエスト(TaskQueueAddRequest)からフィールドの値を取得できる。

var got []byte
ctx = appengine.WithAPICallFunc(ctx, func(ctx context.Context, service, method string, in, out proto.Message) error {
    if service == "taskqueue" && method == "Add" {
        if v, ok := in.(interface{ GetBody() []byte }); ok {
            got = v.GetBody()
        }
    }
    return nil
})
DoSomething(ctx) // 関数内でtaskqueue.Add()が呼ばれる

if !reflect.DeepEqual(got, want) {
    t.Errorf("body = %q, want %q", got, want)
}

なお、フィールドがインポートできない型になっているとinterfaceも作れないのでこの方法は使えない。
それでも必要な場合はencoding/jsonパッケージでMarshalしてからmap[string]interface{}や独自型にUnmarshalすれば良い。