Perjalanan Gocritic ke Masa Lalu


Saya ingin membagikan hasil beberapa hari terakhir, yang meliputi analisis git dari sejarah beberapa proyek Go besar dengan tujuan menemukan komitmen yang memperbaiki kesalahan dan kemudian memformalkannya untuk deteksi jika muncul dalam kode baru.


Pada bagian kedua artikel, kami akan mempertimbangkan beberapa diagnostik baru dalam go-kritik , yang memungkinkan Anda menemukan kode yang sangat mungkin mengandung kesalahan.


Cari ide di masa lalu


Untuk menemukan ide untuk inspeksi kode baru, Anda dapat mengintip penganalisa statis untuk bahasa pemrograman lain, Anda dapat memeriksa kode sumber terbuka secara manual dalam upaya menemukan templat kode yang salah di dalamnya, atau Anda dapat melihat sejarah.


Misalkan Anda sedang memeriksa proyek Go-Files . Setelah memulai linter (penganalisa statis), tidak ada masalah, audit teks sumber juga tidak membawa hasil yang bagus. Haruskah saya buru-buru mengesampingkan Go-Files ? Tidak juga.


Selama keberadaannya, Go-Files memiliki sejumlah cacat yang mudah dideteksi, tetapi sekarang sudah diperbaiki. Yang perlu kita lakukan adalah mulai menganalisis sejarah repositori git.



Bug ada di suatu tempat di dekatnya.


Koleksi komitmen yang menarik bagi kami


Masalahnya:


  • Mungkin ada banyak komitmen
  • Terkadang dalam revisi yang sama hal-hal tidak saling terkait
  • Terkadang komentar pada komit tidak sesuai dengan perubahan yang sebenarnya.
  • Beberapa orang meremehkan penggunaan pesan komit yang baik (jangan arahkan jari)

Pertama-tama, kami ingin mengurangi jumlah informasi yang harus diproses tanpa otomatisasi. Masalah tanggapan negatif palsu dapat diselesaikan dengan meningkatkan ukuran sampel (unduh lebih banyak paket Go ke grup kontrol).


Anda dapat menganalisanya dengan cara yang berbeda, saya baru saja menggunakan git log --grep untuk satu set templat, dengan menaksir terlalu tinggi dan meremehkan skor komit tergantung pada isi dari pesan git log --grep . Jika komit terlalu besar, Anda dapat segera membuangnya, karena sangat mungkin untuk mengetahui apa yang terjadi akan ada nontrivial.


Saya juga ingin menambahkan bahwa di samping koreksi atau menemukan kesalahan cukup sering ada deskripsi yang tidak terlalu optimis dan kadang-kadang tidak cukup kultural tentang tambalan:


 - "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) 

"Perbaikan bug" yang paling tidak berguna adalah mengedit kesalahan sintaks atau masalah lain yang mencegah proyek dari kompilasi bahkan melalui go build . Tidak semua proyek menggunakan CI atau kait pra-komitmen , sehingga revisi yang rusak tersebut terkadang berakhir di cabang utama.


Kesalahan sejarah


Mari kita lihat beberapa kesalahan paling menarik yang ditemukan di balik sampul git log.


Kembalikan nil alih-alih hasil nyata


gin-gonic / gin: fix bug, return err ketika gagal mengikat bool :


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

go-pg / pg: Perbaikan bug AppendParam :


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

Kurangnya pengembalian di jalur cepat


gonum / gonum: bug diperbaiki di jalur cepat dswap :


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

Menjalankan goroutine tanpa wg.Tambahkan (1)


btcsuite / btcd: Perbaiki beberapa bug di shutdown server RPC :


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

Kondisi logis tidak valid


gorgonia / gorgonia: memperbaiki beberapa bug juga :


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

src-d / go-git: plumbing / idxfile: perbaiki pencarian bug di MemoryIndex :


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

go-xorm / xorm: perbaiki bug pada jumlah :


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

btcsuite / btcd: server: Perbaiki bug disconnecting peer on filteradd :


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

btcsuite / btcd: Perbaiki bug uji terbalik dengan penanganan operasi maks :


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

Memperbarui penerima (ini / diri sendiri) lewat nilai


Klasik Salinan objek dimodifikasi di dalam metode, karena itu penelepon tidak akan melihat hasil yang diharapkan.


Memperbaiki bug tumpukan / antrian / deque (penerima pointer) :


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

Memperbarui salinan objek di dalam satu lingkaran


mengandung / traefik: Tetapkan tugas yang difilter ke aplikasi yang terdapat dalam slice :


 -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) 

mengandung / traefik: Tambahkan unit test untuk paket yang aman :


 -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() }) } 

Hasil dari fungsi pembungkus tidak digunakan.


gorgonia / gorgonia: Memperbaiki bug kecil yang tidak penting di Grad () :


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

Pekerjaan dengan make yang salah


Terkadang penjual muda akan " make([]T, count) " diikuti dengan append di dalam loop. Pilihan yang benar di sini adalah " make([]T, 0, count) ".


HouzuoGuo / tiedot: perbaiki bug pemindaian koleksi :


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

Perbaikan di atas memperbaiki kesalahan asli, tetapi memiliki satu kelemahan yang diperbaiki di salah satu dari komit berikut:


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

Tetapi beberapa operasi, seperti copy atau ScanSlice mungkin memerlukan panjang "benar".


go-xorm / xorm: perbaikan bug untuk SumsInt mengembalikan slice kosong :


 -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 { 

Segera pergi-kritik akan membantu menemukan kesalahan di kelas ini.


Kesalahan lainnya


Enumerasi ternyata agak rumit, saya mengambil sisanya di bawah spoiler. Bagian ini opsional, jadi Anda bisa membiarkannya nanti atau melanjutkan.


Saya ingin lebih!


Fragmen berikut ini menarik karena, meskipun faktanya mengoreksi kesalahan, kunci iterable dinamai dengan nama yang sama yang digunakan untuk nilai iterable. Kode baru tidak terlihat benar dan dapat membingungkan programmer berikutnya.


gonum / gonum: Fixed bug dalam fungsi bagian :


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

Hasil yang dinamai adalah variabel biasa yang hampir sama, sehingga hasilnya diinisialisasi sesuai dengan aturan yang sama. Pointer akan memiliki nilai nil dan jika Anda ingin bekerja dengan objek ini, Anda perlu menginisialisasi pointer ini secara eksplisit (misalnya, melalui yang new ).


dgraph-io / dgraph: memperbaiki bug referensi nil pointer di server / main.go :


 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) 

Paling sering dan paling membayangi gigitan ketika berhadapan dengan kesalahan.


btcsuite / btcd: blockchain / indexers: perbaiki bug dalam pengindeksan ulang pengejaran :


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

go-xorm / xorm: fix insert err bug :


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

gin-gonic / gin: Memperbaiki masalah baris baru dengan debugPrint dalam fungsi Run * :


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

Menerapkan mutex pada contoh di bawah ini menuntun saya pada gagasan menulis cek yang melaporkan penggunaan *sync.Mutex sebagai anggota struktur. Dave Cheney merekomendasikan selalu menggunakan nilai mutex , bukan pointer ke sana.


tealeg / xlsx: memperbaiki bug saat memuat spreadsheet :


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



Perang salib melawan kode yang mencurigakan


badCond


Pemeriksaan badCond menemukan ekspresi logis yang berpotensi salah.


Contoh ekspresi logis yang mencurigakan:


  • Hasilnya selalu true atau false
  • Ungkapan ini ditulis dalam bentuk yang hampir tidak bisa dibedakan dari yang keliru

Pemeriksaan ini juga berfungsi pada operasi seperti " x == nil && x == y ". Satu solusi sederhana adalah menulis ulang ke " x == nil && y == nil ". Keterbacaan kode tidak menderita, tetapi linter tidak akan melihat sesuatu yang mencurigakan dalam kode ini.


Berikut adalah contoh perbaikan bug yang dapat ditemukan oleh badCond :


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

Piala:



lemah


weakCond mirip dengan badCond , tetapi tingkat kepercayaan peringatan sedikit lebih rendah.


Kondisi yang lemah adalah kondisi yang tidak mencakup input dengan cukup baik.


Contoh bagus dari kondisi lemah (tidak memadai) adalah memeriksa irisan untuk nil dan kemudian menggunakan elemen pertama. Kondisi " s != nil " tidak cukup di sini. Kondisi yang benar adalah " len(s) != 0 " atau yang setara:


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

Piala:



dupArg


Untuk beberapa fungsi, memberikan nilai (atau variabel) yang sama dengan beberapa argumen tidak masuk akal. Cukup sering, ini menandakan kesalahan salin / tempel .


Contoh dari fungsi tersebut adalah copy . copy(xs, xs) ekspresi copy(xs, xs) tidak masuk akal.


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

Piala:



dupCase


Anda mungkin tahu bahwa di Go Anda tidak dapat menggunakan nilai case duplikat di dalam switch .


Contoh di bawah ini tidak dapat dikompilasi :


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

Kesalahan kompilasi: duplikat case 1 di switch.

Tetapi bagaimana jika kita mengambil contoh yang lebih menarik :


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

Nilai duplikat diizinkan melalui variabel. Selain itu, switch true memberi lebih banyak ruang untuk kesalahan:


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

Dan berikut ini adalah contoh untuk memperbaiki kesalahan nyata:


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

Piala:



dupBranchBody


Pernyataan bersyarat seperti if/else dan switch memiliki badan untuk dieksekusi. Ketika kondisi terpenuhi, kontrol ditransfer ke unit operasi terkait. Sementara diagnostik lain memverifikasi bahwa kondisi blok ini berbeda, dupBranchBody memeriksa bahwa blok yang dapat dieksekusi sendiri tidak sepenuhnya identik.


Kehadiran duplikat badan di dalam pernyataan bersyarat, kecuali jika itu adalah type switch , setidaknya mencurigakan.


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

Penilaian tentang tingkat kebenaran kode di bawah ini diserahkan kepada pembaca:


 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, `{`) } } } 

Badan di dalam " if field.IsString() " dan " else " yang terkait identik.


Piala:



caseOrder


Semua tipe di dalam type switch diurutkan secara berurutan, ke yang kompatibel pertama. Jika nilai tag bertipe *T , dan mengimplementasikan antarmuka I , maka Anda harus meletakkan label dengan " case *T " sebelum label dengan " case I ", jika tidak hanya " case I " yang akan dieksekusi, karena *T kompatibel dengan I (tetapi bukan sebaliknya).


 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)) 

Piala:



offBy1


Kesalahan per unit sangat populer sehingga bahkan memiliki halaman wikipedia sendiri .


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

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

go-critic memiliki opsi terbatas untuk menemukan kesalahan seperti itu, tetapi sejauh ini tidak satu perbaikan telah dikirim ke repositori publik. Berikut adalah beberapa perbaikan yang ditemukan saat melihat cerita:



Sedikit ngeri pada akhirnya


go vet memiliki pemeriksaan yang baik untuk ekspresi seperti " x != a || x != b ". Tampaknya orang mungkin tidak tahu tentang gometalinter , tetapi go vet meluncurkan hampir semua orang, bukan?


Menggunakan utilitas gogrep, saya mengumpulkan daftar kecil ekspresi serupa yang masih di cabang utama dari beberapa proyek.


Untuk kucing paling berani


Saya mengusulkan untuk mempertimbangkan daftar ini dan mengirimkan permintaan penarikan.


 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) 



Belajar dari kesalahan orang lain



Tidak, bahkan pada bagian terakhir tidak akan ada apa-apa tentang pembelajaran mesin .


Tetapi yang akan ditulis di sini adalah tentang golangci-lint , yang mengintegrasikan kritikus dalam salah satu rilis terbaru. golangci-lint adalah analog dari gometalinter , yang dapat Anda baca tentang kelebihannya, misalnya, dalam artikel "Analisis statis di Go: cara kami menghemat waktu saat memeriksa kode . "


go-critic baru-baru ini ditulis ulang menggunakan lintpack . Detail dapat ditemukan di Go lintpack: manajer linter yang dapat dikompilasi .


Jika Anda belum mulai secara aktif menggunakan alat untuk menganalisis program Anda untuk kesalahan potensial, baik gaya dan logis, hari ini saya sarankan sekali lagi memikirkan perilaku Anda sambil minum teh bersama rekan kerja. Anda akan berhasil.


Baik untuk semua

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


All Articles