
Você deve se lembrar do recente anúncio de um novo analisador estático para Go chamado go-critical .
Eu verifiquei o projeto golang / go com ele e enviei alguns patches que corrigem alguns problemas encontrados lá.
Neste artigo, analisaremos o código corrigido e também estaremos motivados a enviar ainda mais alterações ao Go.
Para os mais impacientes: uma lista atualizada de troféus .
dupSubExpr
Todos cometemos erros e, muitas vezes, por desatenção. Go, sendo um idioma no qual você às vezes precisa escrever código chato e clichê, às vezes contribui para erros de digitação e / ou erros de copiar / colar.
CL122776 contém uma correção para um erro encontrado pelo dupSubExpr :
func (a partsByVarOffset) Less(i, j int) bool { - return varOffset(a.slots[a.slotIDs[i]]) < varOffset(a.slots[a.slotIDs[i]]) + return varOffset(a.slots[a.slotIDs[i]]) < varOffset(a.slots[a.slotIDs[j]]) // ^__________________________________^ }
Preste atenção ao índice à esquerda e à direita. Antes da correção, o LHS e o RHS do operador <
eram idênticos e o dupSubExpr
trabalhava para dupSubExpr
.
Se o seu projeto é patrocinado por um sistema de controle de versão , em vez de desabilitar o código envolvendo-o em um comentário, vale a pena excluí-lo completamente. Existem exceções, mas com mais freqüência esse código "morto" interfere, confunde e pode ocultar erros.
commentedOutCode pôde encontrar um fragmento tão interessante ( CL122896 ):
switch arch.Family {
Há um comentário um pouco mais alto:
Se você alternar para a ramificação go1.4
e remover essas 3 linhas do comentário, o código não será compilado; no entanto, se você descomentá-las no assistente, tudo funcionará.
Normalmente, o código oculto em um comentário requer exclusão ou ativação reversa.
De tempos em tempos, é útil visitar esses ecos do passado em seu código.
Sobre as dificuldades de detecçãoEsse é um dos meus cheques favoritos, mas é um dos mais "barulhentos".
Muitos falsos positivos para pacotes que usam math/big
e dentro do compilador. No primeiro caso, esses são geralmente comentários explicativos sobre as operações executadas e, no segundo, uma descrição do código que descreve o fragmento AST. Distinguir esses comentários do código "morto" real sem introduzir falsos negativos não é trivial.
Isso dá origem à idéia: e se concordarmos de alguma forma especializar o código dentro dos comentários, o que é explicativo? Então a análise estática será simplificada. Isso pode ser um pouco que facilitará a definição de um comentário explicativo ou o código Go inválido (por exemplo, se você adicionar um sinal de cerquilha, #
no início da linha).
Outra categoria são comentários com TODO
explícitos. Se o código for removido para comentar, mas houver uma descrição clara de por que isso é feito e quando está planejado corrigir esse trecho de código, é melhor não dar um aviso. Isso já foi implementado, mas poderia funcionar de maneira mais confiável.
boolExprSimplify
Às vezes as pessoas escrevem código estranho. Talvez me pareça, mas expressões lógicas ( booleanas ) às vezes parecem especialmente estranhas.
O Go possui um excelente back-end do montador x86 (aqui uma lágrima caiu), mas o ARM realmente fez errado:
if !(o1 != 0) { break }
“Se não o1 não for igual a 0” ... A dupla negação é um clássico. Se você gostou, eu convido você a se familiarizar com o CL123377 . Lá você pode ver a versão corrigida.
Opção corrigida (para aqueles que não podem ser atraídos para a revisão) - if !(o1 != 0) { + if o1 == 0 {
O boolExprSimplify visa simplificações que aumentam a legibilidade (e o otimizador Go teria de lidar com a questão do desempenho sem ele).
underef
Se você usar o Go a partir de suas versões anteriores, poderá se lembrar dos pontos e vírgulas obrigatórios, da falta de desreferenciação automática de ponteiros e de outros recursos que hoje são quase impossíveis de serem vistos no novo código.
No código antigo, você ainda pode ver algo assim:
Vários gatilhos do analisador underef foram corrigidos no CL122895 .
appendCombine
Você pode saber que o append
pode levar vários argumentos como elementos para adicionar à fatia de destino. Em algumas situações, isso pode melhorar um pouco a legibilidade do código, mas, o que pode ser mais interessante, também pode acelerar o seu programa, já que o compilador não suprime as chamadas append
compatíveis ( cmd / compile: combine append calls ).
No Go, a verificação appendCombine encontrou a seguinte seção:
- for i := len(ip) - 1; i >= 0; i-- { - v := ip[i] - buf = append(buf, hexDigit[v&0xF]) - buf = append(buf, '.') - buf = append(buf, hexDigit[v>>4]) - buf = append(buf, '.') - } + for i := len(ip) - 1; i >= 0; i-- { + v := ip[i] + buf = append(buf, hexDigit[v&0xF], + '.', + hexDigit[v>>4], + '.') + }
name old time/op new time/op delta ReverseAddress-8 4.10µs ± 3% 3.94µs ± 1% -3.81% (p=0.000 n=10+9)
Detalhes no CL117615 .
rangeValCopy
Não é segredo que os valores iterados em um loop de range
são copiados. Para objetos pequenos, digamos, com menos de 64 bytes, você pode nem perceber isso. No entanto, se esse ciclo estiver em um caminho "quente", ou, sobre o qual você iterar, contiver um número muito grande de elementos, a sobrecarga poderá ser tangível.
O Go possui um vinculador bastante lento (cmd / link) e, sem alterações significativas em sua arquitetura, não é possível obter um forte ganho de desempenho. Mas você pode reduzir um pouco sua ineficiência com a ajuda de microoptimizações. Cada porcentagem ou duas contagens.
A verificação rangeValCopy encontrou vários ciclos com cópia de dados indesejados de uma só vez. Aqui estão os mais interessantes deles:
- for _, r := range exports.R { - d.mark(r.Sym, nil) - } + for i := range exports.R { + d.mark(exports.R[i].Sym, nil) + }
Em vez de copiar o próprio R[i]
a cada iteração, recorremos apenas ao único membro de seu interesse, Sym
.
name old time/op new time/op delta Linker-4 530ms ± 2% 521ms ± 3% -1.80% (p=0.000 n=17+20)
A versão completa do patch está disponível em: CL113636 .
namedConst
No Go, infelizmente, as constantes nomeadas, mesmo montadas em grupos, não são interconectadas e não formam uma enumeração ( proposta: spec: add typed enum support ).
Um problema é converter constantes sem tipo para o tipo que você deseja usar como enumeração.
Suponha que você defina um tipo de Color
, que tenha o valor const ColDefault Color = 0
.
Qual desses dois trechos de código você gosta mais?
Se (B)
parecer mais apropriado, a verificação de namedConst ajudará a rastrear o uso de valores constantes nomeados, ignorando a própria constante nomeada.
É assim que o método context.mangle
do pacote html/template
foi transformado:
s := templateName + "$htmltemplate_" + c.state.String() - if c.delim != 0 { + if c.delim != delimNone { s += "_" + c.delim.String() } - if c.urlPart != 0 { + if c.urlPart != urlPartNone { s += "_" + c.urlPart.String() } - if c.jsCtx != 0 { + if c.jsCtx != jsCtxRegexp { s += "_" + c.jsCtx.String() } - if c.attr != 0 { + if c.attr != attrNone { s += "_" + c.attr.String() } - if c.element != 0 { + if c.element != elementNone { s += "_" + c.element.String() } return s
A propósito, às vezes nos links para os patches você encontra discussões interessantes ...
CL123376 é um desses casos.
desagradável
Um recurso da expressão de fatia é que x[:]
sempre idêntico a x
se o tipo x
for uma fatia ou sequência. No caso de fatias, isso funciona para qualquer tipo de elemento []T
Tudo na lista abaixo é o mesmo ( x
- fatia):
unslice encontra expressões de fatia redundantes semelhantes. Essas expressões são prejudiciais, em primeiro lugar, com uma carga cognitiva extra. x[:]
possui semântica bastante significativa no caso de obter uma fatia da matriz. Uma fatia com intervalos padrão não faz nada, exceto ruídos.
Peço um patch no CL123375 .
switchTrue
No CL123378 , " switch true {...}
" é substituído por " switch {...}
".
Ambas as formas são equivalentes, mas a segunda é mais idiomática.
Encontrado verificando switchTrue .
A maioria das verificações estilísticas revela precisamente os casos em que as duas opções são aceitáveis, mas uma delas é mais comum e familiar para mais programadores do Go. Próxima verificação da mesma série.
typeUnparen
Go, como muitas outras linguagens de programação, adora parênteses. Tanto é assim que estou pronto para aceitar qualquer número deles:
type ( t0 int t1 (int) t2 ((int))
Mas o que acontece se você executar o gofmt
?
type ( t0 int t1 (int)
É por isso que typeUnparen existe. Ele encontra no programa todas as expressões de tipo nas quais você pode reduzir o número de colchetes. Tentei enviar o CL123379 , vamos ver como a comunidade o aceitará.
Lisp não gosta de aparelhoAo contrário dos idiomas do tipo C, no Lisp não é tão fácil inserir colchetes inúteis em qualquer lugar. Portanto, em idiomas cuja sintaxe é baseada em expressões S , escrever um programa que não faz nada além de ter um grande número de colchetes é mais difícil do que em outros idiomas.
crítico em movimento

Examinamos apenas uma pequena parte das verificações implementadas. Ao mesmo tempo, sua quantidade e qualidade só evoluirão com o tempo, inclusive graças às pessoas que ingressaram no desenvolvimento .
O go-critical é totalmente gratuito para qualquer uso ( licença MIT ) e também está aberto para sua participação no desenvolvimento do projeto. Envie-nos ideias para verificações, você pode imediatamente com a implementação, relatar bugs e deficiências encontradas, compartilhar suas impressões. Você também pode propor projetos para auditoria ou relatório sobre sua revisão do código Go, essa experiência é inestimável para nós.
Ir contribuindo tema
Você já viu o artigo Ir workshop de contribuição na Rússia ? Esta queda será a segunda rodada. E desta vez, além de um formato e patrocinadores mais bem-sucedidos, teremos uma arma secreta - um analisador estático maravilhoso. Haverá contribuições suficientes!
Mas, na verdade, você pode começar agora (embora seja melhor - um pouco mais tarde, após o congelamento do código ). Se você se sentir confortável antes do próximo workshop, será muito legal, porque temos muito pouco mentores na Rússia.