Qual é uma boa maneira de entender a estrutura geral de uma base de código?


8

Às vezes, é útil no meu trabalho modificar o código-fonte de outra pessoa ou descobrir como desenvolver coisas específicas para o seu próprio aplicativo. No entanto, nem todos os softwares possuem boa documentação.

Qual é uma boa maneira de entender a estrutura geral de uma base de código?

Por exemplo, quais rotinas chama quais rotinas, etc. Eu poderia usar uma ferramenta de documentação como o Doxygen para esse fim, no entanto, estava pensando se há uma estratégia melhor?


5
Acho que essa é uma pergunta razoável, mas também acho que ela provavelmente deveria ser feita aos programadores . não é uma ciência estritamente computacional, mas uma programação mais conceitual.
— Geoff Oxberry

2
Existem técnicas para fazer isso específicas para códigos científicos?
— David Ketcheson

Eu uso o Doxygen porque é fácil e pode fornecer imagens agradáveis ​​com gráficos que mostram como as rotinas estão conectadas. Observe que pode ser necessário criar uma rotina principal que envolva tudo, para obter uma imagem tão completa.
— Allan P. Engsig-Karup

@ DavidKetcheson: Eu não acho que existem métodos para fazer isso específico para códigos científicos. Dada a forte tradição de códigos de pesquisa não documentados, talvez devesse existir.
— Geoff Oxberry

Respostas:


9

Os seguintes segmentos estão relacionados tangencialmente:

Na primeira parte da minha tese, passei 18 meses modificando o código não documentado do Fortran, e uma das primeiras tarefas foi tentar entender a estrutura geral de uma base de código. A coisa mais importante que sugiro que você faça é tomar notas em um arquivo de texto sempre que descobrir algo. Você não precisa reaprender ou redescobrir coisas durante esse processo demorado e frustrante.

No meu caso, não havia "API" para falar, pois os argumentos das funções não eram auto-documentados, porque o programador anterior usava o estilo Fortran 77 e, portanto, identificadores curtos com pouca ou nenhuma sugestão do que Eles significavam. Não houve testes e, como é o Fortran, não há cabeçalhos. Adicionando ainda mais diversão à mistura, havia algumas funções aqui e ali escritas em C ou C ++.

Coisas que funcionaram para mim (supondo que você trabalhe no Linux):

  • grep. Aprenda a amar grep; você o usará no shell para descobrir onde as funções são declaradas e chamadas, e a saída informará em quais arquivos procurar.
  • O comando "find" no seu editor de código favorito ou IDE. Depois que você souber em qual arquivo procurar, o comando "find" economizará seu tempo procurando por chamadas de função.
  • Refatoração agressiva, se você puder alterar a base de código. Fiz os nomes das variáveis ​​se auto-documentarem para não precisar gastar esforço mental reaprendendo coisas que já havia descoberto. Também simplifiquei o design do código, conforme o descobri, tornando-o menos confuso. Não há mais programa principal de 1000 linhas!
  • Comentários agressivos. Novamente, se você puder alterar a base de código, comente as coisas para que você saiba o que descobriu. Eu não usei o Doxygen, mas o Doxygen é bom para isso.
  • nm. Pode ser útil em bibliotecas se você não tiver o código-fonte para elas, mas deseja saber se uma função que você encontrou está nessa biblioteca. No entanto, só funciona se os símbolos da biblioteca não tiverem sido removidos.
  • Percorra o código com um depurador. É muito mais eficiente do que usar printdeclarações. ddde gdbsão ótimos, e em praticamente todos os sistemas Linux existentes. Sinta-se livre para usar seu depurador favorito.
  • Bug dos desenvolvedores. Esta opção é realmente melhor para perguntas muito específicas. Se você for até eles (como eu) e disser: "Eu não entendo o que está acontecendo aqui", eles podem ter pena de você e tentar explicar as coisas em detalhes gerais, mas isso será de uso limitado em a longo prazo. Você terá que fazer o trabalho de perna, porque os desenvolvedores não fizeram isso por você na documentação e na explicação da estrutura de sua base de código por escrito. Os desenvolvedores são realmente bons quando você está realmente preso a coisas pequenas (se eles se lembram do que fizeram).

Coisas que eu gostaria de ter pensado antes, ou simplesmente não eram opções para mim:

  • Doxigênio. Se você ajustar as opções do Doxyfile, o Doxygen gerará automaticamente muita documentação para você, mesmo sem a sintaxe especial para comentários do Doxygen, que pode ser um bom ponto de partida; Eu usei isso em projetos posteriores que encontrei e tem sido incrivelmente útil.
  • Teste de unidade. Se você puder alterar a base de código e tiver alguma idéia do que deve fazer, escreva testes de unidade para várias funções. (É uma habilidade útil para aprender, independentemente.)
  • Se você estiver trabalhando com C / C ++, observe os cabeçalhos.
  • Escreva exemplos de programas. Não é uma opção para mim nesse projeto Fortran, mas tem sido útil para mim ao escolher APIs de terceiros. Além disso, veja os programas de exemplo, se houver algum.
  • Use gcove lcovfaça uma análise de cobertura em execuções típicas do código, se você tiver exemplos ou executáveis ​​para trabalhar. Se houver exemplos que devem executar grandes partes da base de código, essas duas ferramentas combinadas informarão quantas vezes cada linha de código é visitada. É mais útil com os sinalizadores de depuração ativados. Se uma parte do código não é visitada, provavelmente é menos importante entendê-lo imediatamente. Se uma parte do código é visitada muito, provavelmente vale a pena entender o que é. Talvez o código seja executado muito porque é um loop sem importância, ou pode ser uma função importante na qual muitas outras funções dependem. Você não pode dizer apenas sobre a análise de cobertura, mas a análise de cobertura fornece uma idéia de onde você deve concentrar seu tempo.
  • Ferramentas de análise de código estático como splintpodem dizer se algo suspeito está acontecendo no código, como algumas variáveis ​​nunca são usadas.
  • Criação de perfil. Mais uma vez, fornece dados que não informam imediatamente o que é ou não importante, mas sugerem o que pode ser importante. Se você gastar muito tempo na CPU chamando uma função, convém examinar e ver o que ela faz. Você também pode usar a saída de criação de perfil com dote graphvizpara gerar gráficos de chamada e ver quantas vezes as funções são chamadas, como a análise de cobertura. Para códigos complexos, uma análise gráfica pode ser muito mais útil.
  • Se você está trabalhando em C, o Frama-C deve ser útil na análise de código, mas nunca o usei porque parecia muito complicado para valer o esforço. Eu trabalho em C puro, mas na maioria das vezes eu escrevo código; Eu nunca trabalhei com código C não documentado.

2
Qual é a maneira mais comum de entender um aplicativo C ++ muito grande? no Stack Overflow é específico de C ++, mas outra questão existente de alguma antiguidade.
— dmckee --- gatinho ex-moderador

Boa lista. Coloquei tudo no git e uso "git grep". E também o histórico do git para arquivos individuais, se disponível.
— Ondřej Čertík

3

Eu sempre digo aos meus alunos para lerem um código de baixo para cima: você começa em main () e vê o que ele chama. Normalmente, este é apenas um pequeno número de funções. Em seguida, você analisa as funções chamadas de main () que normalmente definem o fluxo geral do algoritmo (loop de tempo, montagem, solucionador, saída, etc). Suba mais ou menos dois níveis para obter uma visão geral do algoritmo a 30.000 pés. O restante pode ser encontrado na documentação do doxygen etc.

Mas como eu disse, a mensagem é: Leia o código de baixo para cima.


3
Seu conselho é bem aceito, mas não parece ler código de baixo para cima. Em vez disso, parece ler de cima para baixo. Main () (ou Programa, no Fortran) é a unidade de programa mais avançada em um código executável. Além disso, seu conselho exige alguns ajustes quando o código em questão é uma biblioteca que define uma API. Não há função main (); nesse caso, você precisa combinar sua estratégia de ver quais funções chamam para decidir quais funções são importantes para você, geralmente observando (ou codificando) programas de exemplo.
— Geoff Oxberry

Pode haver alguma confusão sobre "de baixo para cima" porque os programadores que aprenderam Pascal no início frequentemente colocam o ponto de entrada no final (ou na parte inferior) do arquivo - porque Pascal fez apenas uma passagem e tudo teve que ser apresentado antes dele foi chamado. Como Geoff, no entanto, prefiro a lógica na qual Main () está no "topo" nocional de uma árvore que cresce para baixo.
— dmckee --- gatinho ex-moderador

Sim, eu quis dizer a maneira do dmckee de ter main () no final do arquivo porque, caso contrário, seria necessário encaminhar tudo - declarar tudo.
— Wolfgang Bangerth
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.