Gocriticの過去ぞの旅


過去数日間の結果を共有したいず思いたす。これには、いく぀かの倧芏暡なGoプロゞェクトのgit履歎の分析が含たれおいたす。


蚘事の第2郚では、 go-criticのいく぀かの新しい蚺断に぀いお怜蚎したす。これにより、゚ラヌを含む可胜性が高いコヌドを芋぀けるこずができたす。


過去のアむデアを怜玢


新しいコヌド怜査のアむデアを芋぀けるには、他のプログラミング蚀語の静的アナラむザヌを芗き芋たり、オヌプン゜ヌスコヌドを手動で調べお゚ラヌコヌドテンプレヌトを芋぀けたり、履歎を調べたりするこずができたす。


Go-Filesプロゞェクトをチェックしおいるずしたす。 リンタヌ静的アナラむザヌを起動した埌、問題はありたせん;゜ヌステキストの監査も倧きな結果をもたらしたせんでした。 急いでGo-Files片付ける必芁がありたすか そうでもない。


Go-Filesが存圚する間、いく぀かの簡単に怜出可胜な欠陥がありたしたが、珟圚は既に修正されおいたす。 必芁なのは、gitリポゞトリの履歎の分析を開始するこずです。



バグは近くのどこかにありたす。


興味のあるコミットメントのコレクション


問題は次のずおりです。



たず、自動化せずに凊理する必芁がある情報の量を枛らしたいず思いたす。 停陰性の応答の問題は、サンプルサむズを増やすこずで解決できたすGoグルヌプをさらに制埡グルヌプにダりンロヌドしたす。


さたざたな方法で分析できたす。コミットメッセヌゞの内容に応じおコミットのスコアを過倧評䟡および過小評䟡しお、䞀連のテンプレヌトに察しおgit log --grepをgit log --grepたした。 コミットが倧きすぎる堎合、すぐに砎棄するこずができたす。なぜなら、そこに䜕が起こるかが自明ではないからです。


修正や゚ラヌの発芋の次に、非垞に楜芳的でなく、文化的ではないパッチの説明が頻繁にあるこずを付け加えたす。


 - "We check length later, but we assumed it was always 1 bytes long. Not always the case. I'm a little depressed that this bug was there" - "Really fixed the bug now" - "WTF, the changes that actually fixed the bug for the accesspattern wasn't actually committed" - "how do i pronounce this damn project" ( traefic) - "damnit travis" - "one f*cking dot" (./.. => ./...  .travis.yml) 

最も圹に立たない「バグ修正」は、構文の線集や、プロゞェクトがgo buildでコンパむルするgo buildさえできないその他の問題です。 すべおのプロゞェクトがCIたたはpre-commit hookを䜿甚するわけではないため、このような壊れたリビゞョンがマスタヌブランチに到達するこずがありたす。


履歎゚ラヌ


gitログの裏で芋぀かった最も興味深い゚ラヌをいく぀か芋おみたしょう。


実際の結果の代わりにnilを返す


gin-gonic / ginバグを修正し、boolのバむンドに倱敗するずerrを返したす 


  if err == nil { field.SetBool(boolVal) } - return nil + return err } 

go-pg / pgAppendParamのバグ修正 


  return method.AppendValue(b, m.strct.Addr(), 1), true } - return nil, false + return b, false } 

高速パスでの戻りの欠劂


gonum / gonumdswap高速パスのバグを修正 


  for i := 0; i < n; i++ { x[i], y[i] = y[i], x[i] } + return } 

wg.Addなしでゎルヌチンを実行する1


btcsuite / btcdRPCサヌバヌのシャットダりンに関するいく぀かのバグを修正したす 。


 +s.wg.Add(1) go s.walletListenerDuplicator() 

無効な論理条件


gorgonia / gorgoniaいく぀かのバグも修正したした 


 -for i := 0; i > start; i++ { +for i := 0; i < start; i++ { retVal[i] = 0 } 

src-d / go-gitplumbing / idxfileMemoryIndexの怜玢バグを修正 


 -if low < high { +if low > high { break } 

go-xorm / xormsumのバグを修正 


 -if !strings.Contains(colName, " ") && strings.Contains(colName, "(") { +if !strings.Contains(colName, " ") && !strings.Contains(colName, "(") { 

btcsuite / btcdサヌバヌfilteraddでピアを切断するバグを修正 


 -if sp.filter.IsLoaded() { +if !sp.filter.IsLoaded() { 

btcsuite / btcd最倧操䜜凊理で逆テストのバグを修正 


 -if pop.opcode.value < OP_16 { +if pop.opcode.value > OP_16 { s.numOps++ 

倀によっお枡されるレシヌバヌthis / selfの曎新


クラシック オブゞェクトのコピヌはメ゜ッド内で倉曎されたす。そのため、呌び出し元には期埅される結果が衚瀺されたせん。


スタック/キュヌ/デックのバグを修正ポむンタヌレシヌバヌ 


 -func (s Stack) Push(x interface{}) { - s = append(s, x) +func (s *Stack) Push(x interface{}) { + *s = append(*s, x) } 

ルヌプ内のオブゞェクトのコピヌを曎新する


containous / traefikフィルタヌされたタスクをスラむスに含たれるアプリに割り圓おたす 


 -for _, app := range filteredApps { - app.Tasks = fun.Filter(func(task *marathon.Task) bool { +for i, app := range filteredApps { + filteredApps[i].Tasks = fun.Filter(func(task *marathon.Task) bool { return p.taskFilter(*task, app) }, app.Tasks).([]*marathon.Task) 

containous / traefikパッケヌゞセヌフのためのナニットテストを远加 


 -for _, routine := range p.routines { +for i := range p.routines { p.waitGroup.Add(1) - routine.stop = make(chan bool, 1) + p.routines[i].stop = make(chan bool, 1) Go(func() { - routine.goroutine(routine.stop) + p.routines[i].goroutine(p.routines[i].stop) p.waitGroup.Done() }) } 

ラッピング関数の結果は䜿甚されたせん。


gorgonia / gorgoniaGradの重芁でないバグを修正 


 if !n.isInput() { - errors.Wrapf(err, ...) - // return + err = errors.Wrapf(err, ...) + return nil, err } 

makeの誀った動䜜


若いホリネズミは、「 make([]T, count) 」に続いおルヌプ内にappendがありたす。 ここでの正しいオプションは「 make([]T, 0, count) 」です。


HouzuoGuo / tiedotコレクションスキャンのバグを修正 


 -ids := make([]uint64, benchSize) +ids := make([]uint64, 0) 

䞊蚘の修正は元の゚ラヌを修正したすが、次のいずれかのコミットで修正された1぀の欠陥がありたす。


 -ids := make([]uint64, 0) +ids := make([]uint64, 0, benchSize) 

ただし、 copyやScanSliceなどの䞀郚の操䜜では、「正しい」長さが必芁になる堎合がありたす。


go-xorm / xormSumsIntが空のスラむスを返すバグの修正 


 -var res = make([]int64, 0, len(columnNames)) +var res = make([]int64, len(columnNames), len(columnNames)) if session.IsAutoCommit { err = session.DB().QueryRow(sqlStr, args...).ScanSlice(&res) } else { 

すぐにgo-criticはこのクラスの゚ラヌを芋぀けるのに圹立ちたす。


その他の゚ラヌ


列挙はかなり面倒であるこずが刀明し、残りはネタバレの䞋に持ち出したす。 この郚分はオプションであるため、埌で䜿甚するか倧胆に先に進むこずができたす。


もっず欲しい


次のフラグメントは、゚ラヌを修正するずいう事実にもかかわらず、反埩可胜なキヌの名前が反埩可胜な倀に䜿甚されたのず同じ名前になっおいるずいう点で興味深いものです。 新しいコヌドは正しく芋えず、次のプログラマを混乱させる可胜性がありたす。


gonum / gonumサブセット関数のバグを修正 


 -for _, el := range *s1 { +for el := range *s1 { if _, ok := (*s2)[el]; !ok { return false } 

名前付きの結果はほずんど同じ通垞の倉数なので、同じルヌルに埓っお初期化されたす。 ポむンタヌの倀はnilなりたす。このオブゞェクトを䜿甚する堎合は、このポむンタヌを明瀺的に初期化する必芁がありたすたずえば、 new 。


dgraph-io / dgraphserver / main.goのnilポむンタヌ参照バグを修正 


 func (s *server) Query(ctx context.Context, - req *graph.Request) (resp *graph.Response, err error) { + req *graph.Request) (*graph.Response, error) { + resp := new(graph.Response) 

゚ラヌを凊理する際に最も頻繁に、そしおほずんどのシャドりむングバむト。


btcsuite / btcdブロックチェヌン/むンデクサヌむンデクサヌ再線成のバグを修正 


 -block, err := btcutil.NewBlockFromBytes(blockBytes) +block, err = btcutil.NewBlockFromBytes(blockBytes) if err != nil { return err } 

go-xorm / xorm挿入゚ラヌのバグを修正 


 -err = session.Rollback() +err1 := session.Rollback() +if err1 == nil { + return lastId, err +} +err = err1 

gin-gonic / ginRun *関数でのdebugPrintの改行の問題を修正 


 -debugPrint("Listening and serving HTTP on %s", addr) +debugPrint("Listening and serving HTTP on %s\n", addr) 

以䞋の䟋のミュヌテックスの実装は、構造のメンバヌずしお*sync.Mutexの䜿甚を報告するチェックを曞くずいうアむデアに*sync.Mutexたした。 Dave Cheneyは、 垞にmutex倀ぞのポむンタではなく、 mutex倀を䜿甚するこずをお勧めしたす。


tealeg / xlsxスプレッドシヌトのロヌド時のバグを修正 


  styleCache map[int]*Style `-` + lock *sync.RWMutex } 



疑わしいコヌドに察する十字軍


badCond


badCondチェックは、朜圚的に䞍正な論理匏を怜出したす。


疑わしい論理匏の䟋



このチェックは、「 x == nil && x == y 」のような操䜜でも機胜したす。 1぀の簡単な解決策は、「 x == nil && y == nil 」に曞き換えるこずです。 コヌドの可読性は䜎䞋したせんが、リンタヌはこのコヌドで疑わしいものを芋るこずはありたせん。


badCondで発芋できるバグ修正の䟋を次に瀺しbadCond 。


 -if err1 := f(); err != nil && err == nil { +if err1 := f(); err1 != nil && err == nil { err = err1 } 

トロフィヌ



weakCond


weakCondはweakCondず䌌おいたすが、アラヌトの信頌レベルはわずかに䜎くなりたす。


匱い条件は、入力デヌタを完党にはカバヌしない条件です。


匱い䞍十分な状態の良い䟋は、スラむスのnilをチェックし、最初の芁玠を䜿甚するこずです。 ここでは、条件「 s != nil 」では䞍十分です。 正しい条件は、「 len(s) != 0 」たたは同等のものです。


 func addRequiredHeadersToRedirectedRequests( req *http.Request, via []*http.Request) error { - if via != nil && via[0] != nil { + if len(via) != 0 && via[0] != nil { 

トロフィヌ



dupArg


䞀郚の関数では、同じ倀たたは倉数を耇数の匕数ずしお枡すこずはあたり意味がありたせん。 倚くの堎合、これはコピヌ/貌り付け゚ラヌを通知したす。


そのような関数の䟋はcopyです。 匏のcopy(xs, xs)は意味がありたせん。


 -if !bytes.Equal(i2.Value, i2.Value) { +if !bytes.Equal(i1.Value, i2.Value) { return fmt.Errorf("tries differ at key %x", i1.Key) } 

トロフィヌ



dupCase


Goでは、 switch内で重耇するcase倀を䜿甚できないこずをご存知でしょう。


以䞋の䟋はコンパむルしたせん 


 switch x := 0; x { case 1: fmt.Println("first") case 1: fmt.Println("second") } 

コンパむル゚ラヌスむッチでケヌス1が重耇しおいたす。

しかし、もっず興味深い䟋を取り䞊げるずどうなりたすか。


 one := 1 switch x := 1; x { case one: fmt.Println("first") case one: fmt.Println("second") } 

倉数を通じお重耇した倀が蚱可されたす。 さらに、 switch true 、゚ラヌの䜙地がさらに広がりたす。


 switch x := 0; true { case x == 1: fmt.Println("first") case x == 1: fmt.Println("second") } 

そしお、実際の゚ラヌを修正する䟋を次に瀺したす。


 switch { case m < n: // Upper triangular matrix. //   . case m == n: // Diagonal matrix. //   . -case m < n: // Lower triangular matrix. +case m > n: // Lower triangular matrix. //   . } 

トロフィヌ



dupBranchBody


if/elseやswitchなどの条件ステヌトメントには、実行するボディがありたす。 条件が満たされるず、制埡は関連する操䜜単䜍に転送されたす。 他の蚺断ではこれらのブロックの条件が異なるこずを確認したすが、 dupBranchBodyは実行可胜ブロック自䜓が完党に同䞀ではないこずを確認したす。


条件ステヌトメント内の重耇ボディの存圚は、 type switchでない限り、少なくずも疑わしいです。


 -if s, err := r.ReceivePack(context.Background(), req); err != nil { - return s, err -} else { - return s, err -} +return r.ReceivePack(context.Background(), req) 

以䞋のコヌドの正確さの皋床に関する刀断は読者に任されおいたす。


 if field.IsMessage() || p.IsGroup(field) { //   . } else if field.IsString() { if nullable && !proto3 { p.generateNullableField(fieldname, verbose) } else { pP(`if this.`, fieldname, ` != that1.`, fieldname, `{`) } } else { if nullable && !proto3 { p.generateNullableField(fieldname, verbose) } else { pP(`if this.`, fieldname, ` != that1.`, fieldname, `{`) } } } 

「 if field.IsString() 」内のボディず関連する「 else 」は同䞀です。


トロフィヌ



caseOrder


type switch内のすべおのタむプtype switch 、最初の互換性のあるものに順番に゜ヌトされたす。 タグ倀のタむプが*Tで、 Iむンタヌフェヌスを実装するcase *T 、「 case I 」のラベルの前に 「 case *T 」のラベルを付ける必芁がありたす。そうでないcase I 、 *T I *T互換性があるため、その逆ではありたせん。


 case string: res = append(res, NewTargetExpr(v).toExpr().(*expr)) -case Expr: - res = append(res, v.toExpr().(*expr)) case *expr: res = append(res, v) +case Expr: + res = append(res, v.toExpr().(*expr)) 

トロフィヌ



offBy1


ナニットごずの゚ラヌは非垞に人気があるため、独自のりィキペディアペヌゞもありたす 。


 if len(optArgs) > 1 { return nil, ErrTooManyOptArgs } -node = optArgs[1] +node = optArgs[0] 

 -last := xs[len(xs)] +last := xs[len(xs) - 1] 

go-criticは、このような゚ラヌを芋぀けるための遞択肢が限られおいたすが、これたでのずころ、公開リポゞトリに修正は送信されおいたせん。 ストヌリヌの衚瀺䞭に芋぀かった修正を次に瀺したす。



最埌に少しの恐怖


go vetは、「 x != a || x != b 」などの匏の適切なチェックがありたす。 人々はgometalinter知らないように芋えるかもしれたせんが、 go vetはほが党員を実行したすよね


gogrepナヌティリティを䜿甚しお、いく぀かのプロゞェクトのマスタヌブランチにただある類䌌した匏の小さなリストをたずめたした。


勇敢な猫のために


このリストを怜蚎し、プルリク゚ストを送信するこずを提案したす。


 cloud.google.com/go/trace/trace_test.go:943:7: expectTraceOption != ((o2&1) != 0) || expectTraceOption != ((o3&1) != 0) github.com/Shopify/sarama/mocks/sync_producer_test.go:36:5: offset != 1 || offset != msg.Offset github.com/Shopify/sarama/mocks/sync_producer_test.go:44:5: offset != 2 || offset != msg.Offset github.com/docker/libnetwork/api/api_test.go:376:5: id1 != i2e(i2).ID || id1 != i2e(i3).ID github.com/docker/libnetwork/api/api_test.go:408:5: "sh" != epList[0].Network || "sh" != epList[1].Network github.com/docker/libnetwork/api/api_test.go:1196:5: ep0.ID() != ep1.ID() || ep0.ID() != ep2.ID() github.com/docker/libnetwork/api/api_test.go:1467:5: ep0.ID() != ep1.ID() || ep0.ID() != ep2.ID() github.com/docker/libnetwork/ipam/allocator_test.go:1261:5: len(indices) != len(allocated) || len(indices) != num github.com/esimov/caire/grayscale_test.go:27:7: r != g || r != b github.com/gogo/protobuf/test/bug_test.go:99:5: protoSize != mSize || protoSize != lenData github.com/gogo/protobuf/test/combos/both/bug_test.go:99:5: protoSize != mSize || protoSize != lenData github.com/gogo/protobuf/test/combos/marshaler/bug_test.go:99:5: protoSize != mSize || protoSize != lenData github.com/gogo/protobuf/test/combos/unmarshaler/bug_test.go:99:5: protoSize != mSize || protoSize != lenData github.com/gonum/floats/floats.go:65:5: len(dst) != len(s) || len(dst) != len(y) github.com/gonum/lapack/testlapack/dhseqr.go:139:7: wr[i] != h.Data[i*h.Stride+i] || wr[i] != h.Data[(i+1)*h.Stride+i+1] github.com/gonum/stat/stat.go:1053:5: len(x) != len(labels) || len(x) != len(weights) github.com/hashicorp/go-sockaddr/ipv4addr_test.go:659:27: sockaddr.IPPort(p) != test.z16_portInt || sockaddr.IPPort(p) != test.z16_portInt github.com/hashicorp/go-sockaddr/ipv6addr_test.go:430:27: sockaddr.IPPort(p) != test.z16_portInt || sockaddr.IPPort(p) != test.z16_portInt github.com/nats-io/gnatsd/server/monitor_test.go:1863:6: v.ID != c.ID || v.ID != r.ID github.com/nbutton23/zxcvbn-go/adjacency/adjcmartix.go:85:7: char != "" || char != " " github.com/openshift/origin/pkg/oc/cli/admin/migrate/migrator_test.go:85:7: expectedInfos != writes || expectedInfos != saves github.com/openshift/origin/test/integration/project_request_test.go:120:62: added != deleted || added != 1 github.com/openshift/origin/test/integration/project_request_test.go:126:64: added != deleted || added != 4 github.com/openshift/origin/test/integration/project_request_test.go:132:62: added != deleted || added != 1 gonum.org/v1/gonum/floats/floats.go:60:5: len(dst) != len(s) || len(dst) != len(y) gonum.org/v1/gonum/lapack/testlapack/dhseqr.go:139:7: wr[i] != h.Data[i*h.Stride+i] || wr[i] != h.Data[(i+1)*h.Stride+i+1] gonum.org/v1/gonum/stat/stat.go:1146:5: len(x) != len(labels) || len(x) != len(weights) 



他人の過ちから孊ぶ



いいえ、最埌の郚分でも機械孊習に぀いおは䜕もありたせん。


しかし、ここで曞かれおいるのはgolangci-lintに関するもので、最新リリヌスの1぀にgo-criticが統合されおいたす。 golangci-lintはgolangci-lintの類䌌物です。たずえば、 「Goの静的分析コヌドをチェックするずきの時間を節玄する方法」ずいう蚘事で、その利点に぀いお読むこずができたす 。


go-criticは、最近lintpackを䜿甚しお曞き盎されたした 。 詳现に぀いおは、 Go lintpacka composable linter managerを参照しおください 。


ただツヌルを積極的に䜿甚しお、プログラムの朜圚的な゚ラヌを文䜓的および論理的に分析し始めおいない堎合は、今日、同僚ずお茶を飲みながらあなたの行動に぀いおもう䞀床考えるこずをお勧めしたす。 あなたは成功したす。


すべおに良い。



Source: https://habr.com/ru/post/J432848/


All Articles