Programação limpa ao escrever código científico


169

Eu realmente não escrevo grandes projetos. Não estou mantendo um grande banco de dados ou lidando com milhões de linhas de código.

Meu código é basicamente do tipo "script" - coisas para testar funções matemáticas ou para simular algo - "programação científica". Os programas mais longos em que trabalhei até agora são algumas centenas de linhas de código, e a maioria dos programas em que trabalho são cerca de 150.

Meu código também é uma porcaria. Eu percebi isso outro dia, enquanto tentava encontrar um arquivo que escrevi há um tempo atrás, mas provavelmente substitui e não uso controle de versão, o que provavelmente está fazendo um grande número de vocês se encolher de agonia diante da minha estupidez.

O estilo do meu código é complicado e é preenchido com comentários obsoletos, notando maneiras alternativas de fazer algo ou com linhas de código copiadas. Embora os nomes das variáveis ​​sempre sejam muito bons e descritivos, conforme adiciono ou altero as coisas, por exemplo, algo novo que alguém deseja que seja testado, o código é sobreposto e substituído e porque sinto que essa coisa deve ser testada rapidamente agora que ter um framework, eu começo a usar nomes de variáveis ​​ruins e o arquivo vai para o pote.

No projeto em que estou trabalhando agora, estou na fase em que tudo isso está voltando para me morder muito. Mas o problema é (além de usar o controle de versão e criar um novo arquivo para cada nova iteração e gravar tudo em um arquivo de texto em algum lugar, o que provavelmente ajudará a situação dramaticamente) eu realmente não sei como proceder para melhorar meu estilo de codificação real.

O teste de unidade é necessário para escrever pequenos pedaços de código? E o POO? Que tipos de abordagens são boas para escrever códigos bons e limpos rapidamente ao fazer "programação científica" em vez de trabalhar em projetos maiores?

Eu faço essas perguntas porque, muitas vezes, a programação em si não é super complexa. É mais sobre matemática ou ciências que estou testando ou pesquisando com a programação. Por exemplo, uma classe é necessária quando duas variáveis ​​e uma função provavelmente poderiam cuidar disso? (Considere que essas também são geralmente situações em que a velocidade do programa é preferível no final mais rápido - quando você está executando mais de 25.000.000 etapas de tempo de uma simulação, você meio que quer que seja.)

Talvez isso seja muito amplo e, se for o caso, peço desculpas, mas, olhando os livros de programação, eles costumam ser abordados em projetos maiores. Meu código não precisa de OOP, e já é muito curto, então não é como "oh, mas o arquivo será reduzido em mil linhas se fizermos isso!" Quero saber como "recomeçar" e programar de maneira limpa nesses projetos menores e mais rápidos.

Eu ficaria feliz em fornecer detalhes mais específicos, mas quanto mais gerais forem os conselhos, mais úteis, eu acho. Estou programando em Python 3.


Alguém sugeriu uma duplicata. Deixe-me esclarecer que não estou falando de ignorar completamente os padrões de programação padrão. Claramente, há uma razão para esses padrões existirem. Mas, por outro lado, faz realmente sentido escrever código, ou seja, OOP, quando algumas coisas padrão poderiam ter sido feitas, teriam sido muito mais rápidas de escrever e teriam um nível de legibilidade semelhante devido à falta de programa?

Há exceções. Além disso, provavelmente existem padrões para programação científica além de padrões simples. Estou perguntando sobre isso também. Não se trata de padrões de codificação normais que devem ser ignorados ao escrever código científico; trata-se de escrever código científico limpo!


Atualizar

Apenas pensei em adicionar um tipo de atualização "não muito uma semana depois". Todos os seus conselhos foram extremamente úteis. Agora estou usando o controle de versão - git, com o git kraken para uma interface gráfica. É muito fácil de usar e limpou meus arquivos drasticamente - não há mais necessidade de arquivos antigos, ou versões antigas de código comentadas "por precaução".

Também instalei o pylint e o executei em todo o meu código. Um arquivo obteve uma pontuação negativa inicialmente; Eu nem tenho certeza de como isso foi possível. Meu arquivo principal começou com uma pontuação de ~ 1,83 / 10 e agora está com ~ 9,1 / 10. Todo o código agora está em conformidade com os padrões. Eu também passei por isso com meus próprios olhos, atualizando nomes de variáveis ​​que haviam saído ... uhm ... errados, e procurando seções para refatorar.

Particularmente, fiz uma pergunta recente neste site sobre refatoração de uma das minhas principais funções, e agora ela é muito mais limpa e muito mais curta: em vez de uma função longa, inchada e cheia, ela agora é menos da metade o tamanho e muito mais fácil descobrir o que está acontecendo.

Meu próximo passo é implementar o "teste de unidade". Com o que quero dizer, um arquivo que eu posso executar no meu arquivo principal, que analisa todas as funções nele com instruções assert e try / excpts, que provavelmente não é a melhor maneira de fazê-lo, e resulta em muitos códigos duplicados, mas continuarei lendo e tentarei descobrir como fazê-lo melhor.

Também atualizei significativamente a documentação que já havia escrito e adicionei arquivos suplementares como uma planilha do Excel, a documentação e um documento associado ao repositório do github. Agora parece um projeto de programação real.

Então ... acho que isso é tudo a dizer: obrigado .




8
Se você deseja melhorar ativamente seu código, considere publicar alguns na Revisão de Código . A comunidade de lá terá prazer em ajudá-lo com isso.
Hoffmale 6/07

7
Quando você diz "além de usar o controle de versão e criar um novo arquivo para cada nova iteração e gravar tudo em um arquivo de texto em algum lugar" por "e" você quer dizer "ou" porque se você estiver usando o controle de versão, não deve seja copiar e colar versões. O ponto é que o controle de versão mantém toda a versão antiga para você
Richard Tingle

2
@ Matthreadler Eu não acho que você entende. Sim, só eu provavelmente vou ler e mexer com o código (embora você nunca saiba, e eu tenho alguém com quem estou trabalhando que possa programar, embora em um idioma diferente) ... mas o código ainda está porcaria. Vou ter que ler mais tarde e descobrir novamente o que diabos estou fazendo. Ele é um problema, e posso testemunhar a ele, porque eu estou experimentando os efeitos agora, e as coisas tornaram-se mais fácil, pois eu já implementado de controle de versão e outras técnicas sugeridas aqui.
Heather

Respostas:


163

Este é um problema bastante comum para os cientistas. Eu já vi muitas coisas, e sempre decorre do fato de que a programação é algo que você escolhe ao lado como uma ferramenta para fazer seu trabalho.

Portanto, seus scripts estão uma bagunça. Vou contra o bom senso e dizer que, assumindo que você esteja programando sozinho, isso não é tão ruim! Você nunca tocará na maior parte do que escreve novamente, portanto, gastar muito tempo para escrever um código bonito em vez de produzir "valor" (para que o resultado do seu script) não faça muito para você.

No entanto, haverá um momento em que você precisará voltar a algo que fez e ver exatamente como algo estava funcionando. Além disso, se outros cientistas precisarem revisar seu código, é realmente importante que seja o mais claro e conciso possível, para que todos possam entendê-lo.

Seu principal problema será a legibilidade, então, aqui estão algumas dicas para melhorar:

Nomes de variáveis:

Os cientistas adoram usar notações concisas. Todas as equações matemáticas geralmente usam letras únicas como variáveis, e eu não ficaria surpreso ao ver muitas e muitas variáveis ​​muito curtas no seu código. Isso prejudica muito a legibilidade. Quando você voltar ao seu código, não se lembrará do que aqueles y, ie x2 representam e passará muito tempo tentando descobrir. Tente nomear suas variáveis ​​explicitamente, usando nomes que representam exatamente o que são.

Divida seu código em funções:

Agora que você renomeou todas as suas variáveis, suas equações parecem terríveis e têm várias linhas.

Em vez de deixá-lo em seu programa principal, mova essa equação para uma função diferente e nomeie-a de acordo. Agora, em vez de ter uma linha de código imensa e bagunçada, você terá instruções breves informando exatamente o que está acontecendo e que equação você usou. Isso melhora o seu programa principal, pois você nem precisa olhar para a equação real para saber o que fez e o próprio código da equação. Como em uma função separada, você pode nomear suas variáveis ​​da maneira que desejar e voltar para as letras únicas mais familiares.

Nesta linha de pensamento, tente descobrir todas as partes do código que representam algo, especialmente se algo é algo que você precisa fazer várias vezes no seu código e divida-as em funções. Você descobrirá que seu código se tornará mais fácil de ler rapidamente e poderá usar as mesmas funções sem escrever mais código.

Confeiteiro, se essas funções forem necessárias em mais de seus programas, você pode simplesmente criar uma biblioteca para elas, e você as terá sempre disponíveis.

Variáveis ​​globais:

Quando eu era iniciante, achava que essa era uma ótima maneira de transmitir dados de que precisava em muitos pontos do meu programa. Acontece que existem muitas outras maneiras de contornar coisas, e as únicas coisas que as variáveis ​​globais fazem é dar dores de cabeça às pessoas, pois se você for para um ponto aleatório do seu programa, nunca saberá quando esse valor foi usado ou editado pela última vez, e localizá-lo será uma dor. Tente evitá-los sempre que possível.

Se suas funções precisarem retornar ou modificar vários valores, faça uma classe com esses valores e passe-os para baixo como parâmetro ou faça com que a função retorne vários valores (com tuplas nomeadas) e atribua esses valores ao código do chamador.

Controle de versão

Isso não melhora diretamente a legibilidade, mas ajuda a fazer tudo o que precede. Sempre que você fizer algumas alterações, comprometa-se com o controle de versão (um repositório Git local será bom o suficiente) e, se algo não funcionar, observe o que você mudou ou apenas retroceda! Isso facilitará a refatoração do código e será uma rede de segurança se você quebrar acidentalmente as coisas.

Manter tudo isso em mente permitirá que você escreva um código claro e mais eficaz, além de ajudá-lo a encontrar possíveis erros mais rapidamente, pois você não precisará percorrer funções gigantescas e variáveis ​​complicadas.


56
Excelentes conselhos. No entanto, não posso votar pelo comentário "que não é tão ruim". Isso é tão ruim. Os scripts científicos de baixa qualidade são um grande problema para a reprodutibilidade na análise de dados e uma fonte frequente de erro na análise. Escrever um bom código é valioso não apenas para que você possa entendê-lo mais tarde, mas também para evitar erros em primeiro lugar.
Jack Aidley

22
Se o código for uma implementação de uma fórmula conhecida, nomes de variáveis ​​de uma letra e isso pode ser a coisa certa a fazer. Depende da audiência ... e mais ao ponto, se é esperado que o leitor já saiba o que os nomes significam.
cHao 6/07/2018

11
@cHao o problema não é quando essas variáveis ​​são conectadas à fórmula (daí o conselho "renomeie-as dentro da função"), mas quando elas são lidas e manipuladas fora dela e quando começam a entrar em conflito com outras variáveis ​​na linha (por exemplo, eu vi pessoas que necessitam de três variáveis "x" nomeá-los x1, x2, x3)
BgrWorker

4
"Eu vou contra o senso comum ..." Não, você não é. Você está indo contra o dogma predominante, que é ele próprio contra o senso comum. ;) Este é um conselho perfeitamente correto.
jpmc26

3
Como (ex) programador científico, geralmente adoto uma regra de três. Se eu terminar de escrever código semelhante três vezes, a funcionalidade é aprimorada e escrita em um módulo separado com documentação (geralmente apenas comentários, mas isso é suficiente). Isso limita o caos da programação ad-hoc e me permite construir uma biblioteca que eu possa expandir no futuro.
Rclyly # 9/18

141

Físico aqui. Esteve lá.

Eu argumentaria que seu problema não é sobre a escolha de ferramentas ou paradigmas de programação (teste de unidade, OOP, qualquer que seja). É sobre a atitude , a mentalidade. O fato de os nomes das variáveis ​​serem bem escolhidos no início e acabarem sendo uma porcaria é revelador o suficiente. Se você pensa no seu código como "execute uma vez e jogue fora", inevitavelmente será uma bagunça. Se você pensar nisso como o produto do artesanato e do amor, será lindo.

Acredito que há apenas uma receita para escrever código limpo: escreva para o ser humano que vai lê-lo, não para o intérprete que o executará. O intérprete não se importa se o seu código está uma bagunça, mas o leitor humano se importa.

Você é um cientista. Você provavelmente pode gastar muito tempo polindo um artigo científico. Se seu primeiro rascunho parecer complicado, você o refatorará até que a lógica flua da maneira mais natural. Você quer que seus colegas o leiam e achem os argumentos claros. Você quer que seus alunos possam aprender com isso.

Escrever código limpo é exatamente o mesmo. Pense no seu código como uma explicação detalhada de um algoritmo que, por acaso, é legível por máquina. Imagine que você irá publicá-lo como um artigo que as pessoas lerão. Você vai mostrá-lo em uma conferência e guiar o público linha por linha. Agora ensaie sua apresentação . Sim, linha por linha ! Embaraçoso, não é? Portanto, limpe seus slides (err ... quero dizer, seu código) e ensaie novamente. Repita até que você esteja feliz com o resultado.

Seria ainda melhor se, após os ensaios, você pudesse mostrar seu código para pessoas reais, em vez de apenas pessoas imaginárias e seu futuro eu. Passar por ela linha por linha é chamado de "caminhada de código", e não é uma prática boba.

Claro, tudo isso tem um custo. Escrever código limpo leva muito mais tempo do que escrever código descartável. Somente você pode avaliar se os benefícios superam o custo para seu caso de uso específico.

Quanto às ferramentas, eu disse antes que eles não são que importante. No entanto, se eu tivesse que escolher um, diria que o controle de versão é o mais útil.


32
«Escrever código limpo é exatamente o mesmo [como escrever um artigo claro].» Eu apoio totalmente isso, bem colocado!
21418 juandesant

43
Isso é algo que a maioria dos programadores profissionais esquece de dizer porque é muito óbvio. Seu código é finalizado quando legível e modificável por outro programador. Não quando é executado e produz a saída correta. O OP precisa gastar uma hora extra por refatoração de script e comentar seu código para torná-lo legível por humanos.
UEFI

31
Embora escrever código limpo leve muito mais tempo do que escrever código descartável, muito mais importante é que a leitura de código descartável leva muito mais tempo do que ler código limpo.
User949300

3
@UEFI Não, é algo que a maioria dos programadores profissionais nem percebe. Ou não se importa.
Jpmc26

2
Concordo 100%. O estatístico virou programador, então eu faço uma boa quantidade de programação 'científica' no trabalho. Código claro, com comentários significativos, é um salva-vidas quando você precisa voltar ao código 1, 4 ou 12 meses depois. A leitura do código informa o que o código está fazendo. A leitura dos comentários indica o que o código deve estar fazendo.
Railsdog

82

O controle de versão provavelmente lhe dará o melhor retorno possível. Não é apenas para armazenamento de longo prazo, é ótimo para rastrear suas experiências de curto prazo e voltar para a última versão que funcionou, mantendo anotações ao longo do caminho.

Em seguida, os mais úteis são os testes de unidade. O problema dos testes de unidade é que mesmo as bases de código com milhões de linhas de código são testadas por unidade, uma função de cada vez. O teste de unidade é feito no pequeno, no nível mais baixo de abstração. Isso significa que basicamente não há diferença entre os testes de unidade escritos para pequenas bases de código e os usados ​​para grandes. Há apenas mais deles.

Os testes de unidade são a melhor maneira de evitar quebrar algo que já estava funcionando quando você corrige outra coisa ou, pelo menos, informar rapidamente quando o faz. Eles são realmente mais úteis quando você não é um programador tão habilidoso, ou não sabe como ou não deseja escrever um código mais detalhado, estruturado para tornar os erros menos prováveis ​​ou mais óbvios.

Entre o controle de versão e a gravação de testes de unidade à medida que avança, seu código se tornará naturalmente muito mais limpo. Outras técnicas para codificação mais limpa podem ser aprendidas quando você atinge um platô.


78
Eu religiosamente testo a maior parte do meu código, mas achei o código exploratório e científico menos inútil . A metodologia fundamentalmente não parece funcionar aqui. Não conheço nenhum cientista computacional em meu campo que teste sua unidade de código de análise. Não sei ao certo qual é o motivo dessa incompatibilidade, mas um dos motivos é certamente que, exceto por unidades triviais, é difícil ou impossível estabelecer bons casos de teste.
Konrad Rudolph

22
@KonradRudolph O truque nesses casos provavelmente será uma separação clara das preocupações entre partes do seu código que têm um comportamento claramente definível (leia esta entrada, calcule esse valor) das partes do seu código que são genuinamente exploratórias ou estão se adaptando, por exemplo, alguma saída ou visualização legível por humanos. O problema aqui provavelmente é que a má separação de preocupações leva a desfocar essas linhas, o que leva a uma percepção de que o teste de unidade neste contexto é impossível, o que leva você de volta ao início em um ciclo repetido.
Ant P

18
Em uma nota lateral, o controle de versão também funciona muito bem para documentos LaTeX, pois o formato é passível de diferenciação de texto. Dessa forma, você pode ter um repositório para seus documentos e o código que os suporta. Eu sugiro olhar para o controle de versão distribuído, como o Git. Há um pouco de uma curva de aprendizado, mas depois que você o entende, você tem uma boa maneira limpa de interagir no seu desenvolvimento e tem algumas opções interessantes para usar uma plataforma como o Github, que oferece contas de equipe gratuitas para acadêmicos .
Dan Bryant

12
@ AntP É possível que simplesmente não exista muito código que possa ser significativamente refatorado em unidades testáveis ​​bem definidas. Um monte de código científico está essencialmente reunindo várias bibliotecas. Essas bibliotecas já serão bem testadas e estruturadas de maneira limpa, o que significa que o autor só precisa escrever "cola" e, na minha experiência, é quase impossível escrever testes de unidade para cola que não sejam tautológicos.
21418 James_pic

7
"Entre o controle de versão e a gravação de testes de unidade à medida que você avança, o código se tornará naturalmente muito mais limpo". Isto não é verdade. Eu posso atestar isso pessoalmente. Nenhuma dessas ferramentas impede você de escrever códigos ruins, e, em particular, escrever testes ruins por cima de códigos ruins apenas torna a limpeza ainda mais difícil. Os testes não são uma bala mágica de prata, e falar como eles são é uma coisa terrível a se fazer com qualquer desenvolvedor que ainda esteja aprendendo (que é todo mundo). O controle de versão geralmente nunca causa danos ao próprio código, como fazem os testes ruins.
Jpmc26 06/07/19

29

(além de usar o controle de versão e criar um novo arquivo para cada nova iteração e gravar tudo em um arquivo de texto em algum lugar, o que provavelmente ajudará a situação de maneira dramática)

Você provavelmente já deve ter percebido isso, mas se precisar " gravar tudo isso em um arquivo de texto em algum lugar ", não estará utilizando o sistema de controle de versão em todo o seu potencial. Use algo como Subversion, git ou Mercurial e escreva uma boa mensagem de confirmação com cada confirmação e você terá um log que serve ao propósito do arquivo de texto, mas não pode ser separado do repositório.

Além disso, usar o controle de versão é a coisa mais importante que você pode fazer por um motivo que nenhuma das respostas existentes menciona: reprodutibilidade dos resultados . Se você pode usar suas mensagens de log ou adicionar uma nota aos resultados com o número da revisão, pode ter certeza de que poderá gerar novamente os resultados e estará melhor posicionado para publicar o código no jornal.

O teste de unidade é necessário para escrever pequenos pedaços de código? E o POO? Que tipos de abordagens são boas para escrever códigos bons e limpos rapidamente ao fazer "programação científica" em vez de trabalhar em projetos maiores?

O teste de unidade nunca é necessário, mas é útil se (a) o código for modular o suficiente para que você possa testar as unidades e não a coisa toda; (b) você pode criar testes. Idealmente, você seria capaz de escrever manualmente a saída esperada, em vez de gerá-la com o código, embora gerá-la por código possa pelo menos fornecer testes de regressão que informam se algo mudou seu comportamento. Apenas considere se os testes têm mais probabilidade de serem defeituosos do que o código que estão testando.

OOP é uma ferramenta. Use-o se ajudar, mas não é o único paradigma. Suponho que você realmente conhece a programação procedural: se esse for o caso, no contexto descrito, acho que você se beneficiaria mais com o estudo da programação funcional do que com o POO e, em particular, com a disciplina de evitar efeitos colaterais sempre que possível. Python pode ser escrito em um estilo muito funcional.


4
+1 para enviar mensagens; são como comentários que não podem ser desatualizados porque estão vinculados à versão do código quando eram realmente aplicáveis. Para entender seu código antigo, é mais fácil examinar o histórico do projeto (se as alterações forem confirmadas com uma granularidade razoável) do que ler comentários desatualizados.
Silly Freak

Subversion, git e Mercurial não são fungíveis. Eu defenderia fortemente o uso do Git (ou Mercurial) com um repositório local sobre o Subversion. Com um codificador solo, falhas do Subversion são menos de um problema, mas não é uma grande ferramenta para o desenvolvimento colaborativo e que potencialmente podem acontecer na pesquisa
mcottle

2
@ Mcottle, eu pessoalmente prefiro o git, mas não achei que esse fosse o lugar certo para entrar em detalhes sobre as diferenças, especialmente porque a escolha é uma das guerras religiosas ativas. É melhor incentivar o OP a usar algo do que afastá-lo da área, e a decisão não é permanente em nenhum caso.
Peter Taylor

21

Na faculdade, escrevi alguns códigos pesados ​​para algoritmos. É um pouco difícil de quebrar. Em outras palavras, muitas convenções de programação são criadas com base na idéia de colocar informações em um banco de dados, recuperá-las no momento certo e depois massagear esses dados para apresentá-los a um usuário, normalmente usando uma biblioteca para qualquer matemática ou partes pesadas em algoritmos desse processo. Para esses programas, tudo o que você ouviu sobre OOP, dividindo o código em funções curtas e tornando tudo facilmente compreensível, sempre que possível, é um excelente conselho. Mas isso não funciona muito para códigos pesados ​​de algoritmos, ou códigos que implementam cálculos matemáticos complexos e pouco mais.

Se você estiver escrevendo scripts para realizar cálculos científicos, provavelmente terá documentos com as equações ou algoritmos que você usa escritos neles. Se você estiver usando novas idéias que descobriu por conta própria, espero publicá-las em documentos de sua preferência. Nesse caso, a regra é: você deseja que seu código seja o mais parecido possível com as equações publicadas. Aqui está uma resposta no Software Engineering.SE com mais de 200 votos positivos advogando essa abordagem e explicando como ela é: Existe uma desculpa para nomes curtos de variáveis?

Como outro exemplo, existem alguns trechos de código excelentes no Simbody , uma ferramenta de simulação de física usada para pesquisa e engenharia de física. Esses trechos têm um comentário mostrando uma equação sendo usada para um cálculo, seguido por um código que lê o mais próximo possível das equações implementadas.

ContactGeometry.cpp:

// t = (-b +/- sqrt(b^2-4ac)) / 2a
// Discriminant must be nonnegative for real surfaces
// but could be slightly negative due to numerical noise.
Real sqrtd = std::sqrt(std::max(B*B - 4*A*C, Real(0)));
Vec2 t = Vec2(sqrtd - B, -sqrtd - B) / (2*A);

ContactGeometry_Sphere.cpp:

// Solve the scalar Jacobi equation
//
//        j''(s) + K(s)*j(s) = 0 ,                                     (1)
//
// where K is the Gaussian curvature and (.)' := d(.)/ds denotes differentiation
// with respect to the arc length s. Then, j is the directional sensitivity and
// we obtain the corresponding variational vector field by multiplying b*j. For
// a sphere, K = R^(-2) and the solution of equation (1) becomes
//
//        j  = R * sin(1/R * s)                                        (2)
//          j' =     cos(1/R * s) ,                                      (3)
//
// where equation (2) is the standard solution of a non-damped oscillator. Its
// period is 2*pi*R and its amplitude is R.

// Forward directional sensitivity from P to Q
Vec2 jPQ(R*sin(k * s), cos(k * s));
geod.addDirectionalSensitivityPtoQ(jPQ);

// Backwards directional sensitivity from Q to P
Vec2 jQP(R*sin(k * (L-s)), cos(k * (L-s)));
geod.addDirectionalSensitivityQtoP(jQP);

9
Mais um para fazer "o código ler tanto quanto possível as equações publicadas". Desculpe, defensores de nomes de variáveis ​​longos e significativos. Os nomes mais significativos do código científico, muitas vezes, são desagradáveis, curtos e brutais, exatamente porque essa é exatamente a convenção usada em um jornal de periódico científico que o código está tentando implementar. Para um pedaço de código pesado em termos de equações que implementa as equações encontradas em um artigo de jornal, muitas vezes é melhor ficar o mais próximo possível da nomenclatura do artigo e, se isso for contrário aos bons padrões de codificação, é difícil.
David Hammen

@DavidHammen: Como estudante de graduação, eu respeito isso. Como programador, eu insistiria em que você tenha um bloco gigante de comentários na parte superior de cada função, descrevendo em inglês simples (ou idioma de sua escolha) o que cada variável representava, mesmo que apenas um espaço temporário. Dessa forma, pelo menos eu tenho uma referência para olhar para trás.
tonysdg

1
@DavidHammen Além disso, o apoio Python para UTF-8 em arquivos de origem e regras simples para nomes de variáveis facilita a declarar λou φem vez do feio lambda_ou phy...
Mathias Ettinger

1
@tonysdg Você já tem uma referência; chama-se "Hammen, et al. (2018)" (ou o que seja). Explicará os significados das variáveis ​​com muito mais detalhes do que qualquer bloco de comentários jamais poderia. O motivo para manter os nomes das variáveis ​​próximos à notação no documento é justamente para facilitar a conexão do conteúdo do documento com o código.
Ninguém

17

Então, meu trabalho diário é na publicação e preservação de dados de pesquisa para o sistema da Universidade da Califórnia. Algumas pessoas mencionaram a reprodutibilidade, e acho que esse é realmente o principal problema aqui: documentar seu código da maneira que você documentaria qualquer outra coisa que alguém precise para reproduzir seu experimento e, idealmente, escrever um código que o torne mais fácil para os outros para reproduzir sua experiência e verificar seus resultados quanto a fontes de erro.

Mas algo que eu não vi mencionado, que acho importante, é que as agências de financiamento estão cada vez mais olhando para a publicação de software como parte da publicação de dados e tornando a publicação de software um requisito para a ciência aberta.

Para esse fim, se você deseja algo específico, direcionado a pesquisadores e não a desenvolvedores de software em geral, não posso recomendar a organização Software Carpentry o suficiente. Se você pode participar de um de seus workshops , ótimo; se tudo o que você tem tempo / acesso para fazer é ler alguns de seus artigos sobre as melhores práticas de computação científica , isso também é bom. Do último:

Os cientistas geralmente desenvolvem seu próprio software para esses propósitos, porque isso requer um conhecimento substancial específico do domínio. Como resultado, estudos recentes descobriram que os cientistas normalmente gastam 30% ou mais do seu tempo desenvolvendo software. No entanto, 90% ou mais deles são autodidatas e, portanto, não são expostos a práticas básicas de desenvolvimento de software, como escrever código de manutenção, usar controle de versão e rastreadores de problemas, revisões de código, testes de unidade e automação de tarefas.

Acreditamos que o software é apenas outro tipo de aparato experimental e deve ser construído, verificado e utilizado com o mesmo cuidado que qualquer aparato físico. No entanto, embora a maioria dos cientistas tenha o cuidado de validar seus equipamentos de laboratório e de campo, a maioria não sabe o quão confiável é seu software. Isso pode levar a erros graves, impactando as conclusões centrais da pesquisa publicada. ...

Além disso, como o software geralmente é usado para mais de um projeto e é reutilizado por outros cientistas, os erros de computação podem ter impactos desproporcionais no processo científico. Esse tipo de impacto em cascata causou várias retrações proeminentes quando um erro do código de outro grupo não foi descoberto até depois da publicação.

Um esboço de alto nível das práticas que eles recomendam:

  1. Escreva programas para pessoas, não para computadores
  2. Deixe o computador fazer o trabalho
  3. Faça alterações incrementais
  4. Não se repita (ou outros)
  5. Planejar erros
  6. Otimize o software somente depois que ele funcionar corretamente
  7. Projeto e finalidade de documentos, não mecânica
  8. Colaborar

O artigo entra em detalhes consideráveis ​​em cada um desses pontos.


16

Realmente faz sentido escrever código, digamos OOP, quando algumas coisas padrão poderiam ter sido feitas, seriam muito mais rápidas de escrever e teriam um nível de legibilidade semelhante devido à falta de programa?

Resposta pessoal:
Eu também faço muitos scripts para fins científicos. Para scripts menores, simplesmente tento seguir as boas práticas gerais de programação (ou seja, usar controle de versão, praticar autocontrole com nomes de variáveis). Se estou apenas escrevendo algo para abrir ou visualizar rapidamente um conjunto de dados, não me incomodo com OOP.

Resposta geral:
"Depende." Mas se você está tentando descobrir quando usar um conceito ou paradigmas de programação, aqui estão algumas coisas para pensar:

  • Escalabilidade: o script será autônomo ou será utilizado em um programa maior? Se sim, a programação maior está usando OOP? O código do seu script pode ser facilmente integrado ao programa maior?
  • Modularidade: em geral, seu código deve ser modular. No entanto, o OOP divide o código em partes de uma maneira muito especial. Esse tipo de modularidade (ou seja, dividir seu script em classes) faz sentido para o que você está fazendo?

Quero saber como "recomeçar" e programar de maneira limpa nesses projetos menores e mais rápidos.

# 1: Familiarize-se com o que está por aí:
mesmo que você seja "apenas" script (e realmente se preocupe com o componente científico), reserve um tempo para aprender sobre os diferentes conceitos e paradigmas de programação. Dessa forma, você pode ter uma idéia melhor do que deve / não deve usar e quando. Isso pode parecer um pouco assustador. E você ainda pode ter a pergunta: "Onde eu começo / o que eu começo a olhar?" Tento explicar um bom ponto de partida nos próximos dois pontos.

# 2: Comece a consertar o que você sabe que está errado:
Pessoalmente, eu começaria com as coisas que sei que estão erradas. Obtenha algum controle de versão e comece a se disciplinar para melhorar com esses nomes de variáveis ​​(é uma luta séria). Consertar o que você sabe que está errado pode parecer óbvio. No entanto, na minha experiência, descobri que consertar uma coisa me leva a outra coisa, e assim por diante. Antes que eu perceba, eu revelei 10 coisas diferentes que estava fazendo de errado e descobri como corrigi-las ou implementá-las de maneira limpa.

Nº 3: obtenha um parceiro de programação:
se "começar de novo" para você não envolver aulas formais, considere fazer uma parceria com um desenvolvedor e pedir que ele revise seu código. Mesmo que eles não entendam a parte científica do que você está fazendo, eles poderão lhe dizer o que você poderia ter feito para tornar seu código mais elegante.

Nº 4: procure consórcios:
não sei em que área científica você está. Mas, dependendo do que você faz no mundo científico, tente procurar consórcios, grupos de trabalho ou participantes de conferências. Então veja se existem padrões nos quais eles estão trabalhando. Isso pode levar você a alguns padrões de codificação. Por exemplo, eu faço muito trabalho geoespacial. Observar os documentos da conferência e os grupos de trabalho me levou ao Consórcio Geoespacial Aberto . Uma das coisas que eles fazem é trabalhar em padrões para o desenvolvimento geoespacial.

Espero que ajude!


Nota lateral: Eu sei que você acabou de usar o POO como exemplo. Eu não queria que você pensasse que fiquei preso em como lidar com a escrita de código usando OOP. Era mais fácil escrever uma resposta continuando com esse exemplo.


Eu acho que o número 3 é a questão mais importante - um programador experiente pode dizer ao OP os conceitos de que eles precisam (número 1), como organizar os scripts de uma maneira melhor e como usar o controle de versão (número 2).
Doc Brown

16

Eu recomendo manter o princípio do Unix: Keep It Simple, Stupid! (BEIJO)

Ou, de outra forma: faça uma coisa de cada vez e faça bem.

O que isso significa? Bem, antes de tudo, isso significa que suas funções devem ser curtas. Qualquer função que não possa ser totalmente compreendida em propósito, uso e implementação em alguns segundos é definitivamente muito longa. É provável que você faça várias coisas ao mesmo tempo, cada uma das quais deve ser uma função própria. Então divida.

Em termos de linhas de código, minha heurística é que 10 linhas são uma boa função e qualquer coisa além de 20 é provavelmente uma porcaria. Outras pessoas têm outras heurísticas. A parte importante é manter o comprimento baixo para algo que você possa entender em um instante.

Como você divide uma função longa? Bem, primeiro você procura padrões repetidos de código. Em seguida, você fatora esses padrões de código, atribui a eles um nome descritivo e observa seu código diminuir . Realmente, a melhor refatoração é a refatoração que reduz o tamanho do código.

Isto é especialmente verdade quando a função em questão foi programada com copiar e colar. Sempre que você vê um padrão repetido, sabe instantaneamente que isso provavelmente deve se transformar em uma função própria. Este é o princípio de Não se repita (DRY) . Sempre que você está pressionando copiar e colar, está fazendo algo errado! Crie uma função em seu lugar.

Anedota
: Uma vez, passei vários meses refatorando código com funções de cerca de 500 linhas cada. Depois que eu terminei, o código total foi cerca de mil linhas menor; Eu produzi uma saída negativa em termos de linhas de código. Eu devia à empresa ( http://www.geekherocomic.com/2008/10/09/programmers-salary-policy/index.html ). Ainda assim, acredito firmemente que esse foi um dos meus trabalhos mais valiosos que já fiz ...

Algumas funções podem ser longas porque estão fazendo várias coisas distintas, uma após a outra. Essas não são violações DRY, mas também podem ser divididas. O resultado é frequentemente uma função de alto nível que chama um punhado de funções que implementam as etapas individuais das funções originais. Isso geralmente aumenta o tamanho do código, mas os nomes das funções adicionadas são maravilhosos ao tornar o código mais legível. Porque agora você tem uma função de nível superior com todas as etapas explicitamente nomeadas. Além disso, após essa divisão, fica claro qual etapa opera em quais dados. (Argumentos de função. Você não usa variáveis ​​globais, usa?)

Uma boa heurística para esse tipo de divisão de função secional é sempre que você é tentado a escrever um comentário de seção ou quando encontra um comentário de seção no seu código. Este é provavelmente um dos pontos em que sua função deve ser dividida. O comentário da seção também pode servir para inspirar um nome para a nova função.

Os princípios KISS e DRY podem levar um longo caminho. Você não precisa começar com OOP etc. imediatamente, muitas vezes você pode conseguir grandes simplificações apenas aplicando essas duas. No entanto, a longo prazo, vale a pena conhecer o OOP e outros paradigmas, porque eles oferecem ferramentas adicionais que você pode usar para tornar o código do programa mais claro.

Por fim, registre todas as ações com uma confirmação. Você considera algo em uma nova função, isso é um commit . Você combina duas funções em uma, porque elas realmente fazem a mesma coisa, isso é um commit . Se você renomear uma variável, isso é uma confirmação . Confirme com frequência. Se um dia passa e você não cometeu, provavelmente fez algo errado.


2
Grandes pontos sobre a divisão de métodos longos. Outra boa heurística em relação ao primeiro parágrafo após a anedota: se o seu método puder ser logicamente dividido em seções e você estiver tentado a escrever um comentário explicando o que cada seção faz, deve ser desmembrado nos comentários. Boas notícias, esses comentários provavelmente dão uma boa idéia do que chamar de novos métodos.
9788 Jaques

@ Jaquez Ah, esqueci completamente disso. Obrigado por me lembrar. Eu atualizei a minha resposta para incluir este :-)
cmaster

1
Ótimos pontos, gostaria de simplificar isso para dizer que "DRY" é o fator único mais importante. Identificar "repetições" e removê-las é a pedra angular de quase todas as outras construções de programação. Em outras palavras, todas as construções de programação existem, pelo menos em parte, para ajudá-lo a criar código DRY. Comece dizendo "No Duplication Ever" e depois pratique a identificação e a eliminação. Seja muito aberto quanto ao que poderia ser um duplicado - mesmo se não é um código semelhante poderia ser duplicar a funcionalidade ...
Bill K

11

Concordo com os outros que o controle de versão resolverá muitos dos seus problemas imediatamente. Especificamente:

  • Não há necessidade de manter uma lista de quais alterações foram feitas, ou de ter muitas cópias de um arquivo, etc., pois é disso que o controle de versão cuida.
  • Não há mais arquivos perdidos devido a substituições, etc. (desde que você se atenha ao básico; por exemplo, evite "reescrever o histórico")
  • Não há necessidade de comentários obsoletos, código morto, etc. sendo mantidos "apenas por precaução"; uma vez comprometidos com o controle de versão, fique à vontade para destruí-los. Isso pode parecer muito libertador!

Eu diria que não pense demais: basta usar o git. Atenha-se a comandos simples (por exemplo, apenas um único masterramo), talvez use uma GUI, e você deve ficar bem. Como bônus, você pode usar o gitlab, o github, etc. para publicação e backups gratuitos;)

A razão pela qual escrevi esta resposta foi abordar duas coisas que você pode tentar e que não vi mencionadas acima. A primeira é usar asserções como uma alternativa leve ao teste de unidade. Os testes de unidade tendem a ficar "fora" da função / módulo / o que quer que esteja sendo testado: eles geralmente enviam alguns dados para uma função, recebem um resultado de volta e, em seguida, verificam algumas propriedades desse resultado. Geralmente, é uma boa ideia, mas pode ser inconveniente (especialmente para o código "jogar fora") por alguns motivos:

  • Os testes de unidade precisam decidir quais dados eles fornecerão para a função. Esses dados devem ser realistas (caso contrário, não adianta testá-los), devem ter o formato correto etc.
  • Os testes de unidade devem ter "acesso" ao que eles desejam afirmar. Em particular, os testes de unidade não podem verificar nenhum dado intermediário dentro de uma função; teríamos que separar essa função em pedaços menores, testá-los e conectá-los em outro lugar.
  • Os testes de unidade também são considerados relevantes para o programa. Por exemplo, os conjuntos de testes podem se tornar "obsoletos" se houver grandes alterações desde a última execução e pode haver vários testes de código que nem são mais usados.

As asserções não têm essas desvantagens, pois são verificadas durante a execução normal de um programa. Em particular:

  • Como eles são executados como parte da execução normal do programa, temos dados reais do mundo real para brincar. Isso não requer curadoria separada e (por definição) é realista e possui o formato correto.
  • As asserções podem ser escritas em qualquer lugar do código, para que possamos colocá-las onde quer que tenhamos acesso aos dados que queremos verificar. Se quisermos testar algum valor intermediário em uma função, podemos apenas colocar algumas afirmações no meio dessa função!
  • Como são escritas em linha, as asserções não podem ficar "fora de sincronia" com a estrutura do código. Se garantirmos que as asserções sejam verificadas por padrão, também não precisamos nos preocupar com o fato de elas ficarem "obsoletas", pois veremos imediatamente se elas serão aprovadas na próxima vez em que executarmos o programa!

Você menciona a velocidade como um fator; nesse caso, a verificação de asserção pode ser indesejável nesse loop (mas ainda útil para verificar a configuração e o processamento subsequente). No entanto, quase todas as implementações de asserções fornecem uma maneira de desativá-las; por exemplo, em Python, eles aparentemente podem ser desabilitados executando a -Oopção (eu não sabia disso, pois nunca senti a necessidade de desabilitar nenhuma das minhas afirmações antes). Eu recomendo que você deixá-los empor padrão; se o seu ciclo de codificação / depuração / teste diminuir, é melhor testar com um subconjunto menor de seus dados ou executar menos iterações de alguma simulação durante o teste ou o que for. Se você desabilitar as asserções em execuções sem teste por motivos de desempenho, a primeira coisa que recomendo é avaliar se elas são realmente a fonte da desaceleração! (É muito fácil nos iludir quando se trata de gargalos de desempenho)

Meu último conselho seria usar um sistema de compilação que gerencia suas dependências. Eu pessoalmente uso o Nix para isso, mas também ouvi coisas boas sobre o Guix . Existem também alternativas como o Docker, que são muito menos úteis do ponto de vista científico, mas talvez um pouco mais familiares.

Sistemas como o Nix só recentemente estão se tornando (um pouco) populares, e alguns podem considerá-los um exagero por código de "jogar fora" como você descreve, mas seu benefício para a reprodutibilidade da computação científica é enorme. Considere um script de shell para executar um experimento, como este (por exemplo run.sh):

#!/usr/bin/env bash
set -e
make all
./analyse < ./dataset > output.csv

Podemos reescrevê-lo em uma "derivação" do Nix, assim, como este (por exemplo run.nix):

with import <nixpkgs> {};
runCommand "output.csv" {} ''
  cp -a ${./.} src
  cd src
  make all
  ./analyse < ./dataset > $out
''

O material entre eles ''...''é o código bash, o mesmo que tínhamos antes, exceto que ${...}pode ser usado para "unir" o conteúdo de outras strings (nesse caso ./., que será expandido para o caminho do diretório que contém run.nix). A with import ...linha importa a biblioteca padrão do Nix , que fornece runCommanda execução do código bash. Podemos executar nosso experimento usando nix-build run.nix, o que dará um caminho parecido /nix/store/1wv437qdjg6j171gjanj5fvg5kxc828p-output.csv.

Então, o que isso nos compra? O Nix configurará automaticamente um ambiente "limpo", que só terá acesso às coisas que solicitamos explicitamente. Em particular, ele não tem acesso a variáveis ​​como $HOMEou a qualquer software de sistema que instalamos. Isso torna o resultado independente dos detalhes de nossa máquina atual, como o conteúdo ~/.configou as versões dos programas que instalamos; Também conhecido como material que impede outras pessoas de replicar nossos resultados! Por isso acrescenteicpcomando, já que o projeto não estará acessível por padrão. Pode parecer irritante que o software do sistema não esteja disponível para um script Nix, mas também é o contrário: não precisamos de nada instalado em nosso sistema (que não seja o Nix) para usá-lo em um script; nós apenas pedimos e o Nix irá buscar / compilar / o que for necessário (a maioria das coisas será baixada como binários; a biblioteca padrão também é enorme!). Por exemplo, se queremos um monte de pacotes Python e Haskell específicos, para algumas versões específicas dessas linguagens, além de outras outras porcarias indesejadas (por que não?):

with import <nixpkgs> {};
runCommand "output.csv"
  {
    buildInputs = [
      gcc49 libjson zlib
      haskell.packages.ghc802.pandoc
      (python34.withPackages (pyPkgs: [
        pyPkgs.beautifulsoup4 pyPkgs.numpy pyPkgs.scipy
        pyPkgs.tensorflowWithoutCuda
      ]))
    ];
  }
  ''
    cp -a ${./.} src
    cd src
    make all
    ./analyse < ./dataset > $out
  ''

O mesmo nix-build run.nixexecutará isso, buscando tudo o que solicitamos primeiro (e armazenando tudo em cache, caso desejemos posteriormente). A saída (qualquer arquivo / diretório chamado $out) será armazenada pelo Nix, que é o caminho que ele indica. É identificado pelo hash criptográfico de todas as entradas solicitadas (conteúdo do script, outros pacotes, nomes, sinalizadores do compilador, etc.); esses outros pacotes são identificados por hashes de suas entradas e assim por diante, de forma que tenhamos uma cadeia completa de provinências para tudo, de volta à versão do GCC que compilou a versão do GCC que compilou o bash, e assim por diante!

Espero que eu tenha mostrado que isso nos compra muito por código científico e é razoavelmente fácil de começar. Também está começando a ser levado muito a sério pelos cientistas, por exemplo, (clique no topo do Google) https://dl.acm.org/citation.cfm?id=2830172, por isso pode ser uma habilidade valiosa para cultivar (assim como a programação)


2
Resposta útil muito detalhada - eu realmente gosto das outras respostas, mas as afirmações parecem um primeiro passo muito útil.
Heather

9

Sem recorrer ao tipo de mentalidade de controle de versão completo, embalagem + testes de unidade (que são boas práticas de programação que você deve tentar alcançar em algum momento), uma solução intermediária que eu acho adequada seria usar o Jupiter Notebook . Isso parece se integrar melhor à computação científica.

Tem a vantagem de poder misturar seus pensamentos com o código; explicando por que uma abordagem é melhor que outra e deixando o código antigo como está em uma seção ad-hoc. Além de usar as células corretamente, naturalmente você irá fragmentar seu código e organizá-lo em funções que podem ajudar na sua compreensão.


1
Além disso, isso realmente ajuda na reprodutibilidade - você pode executar exatamente o mesmo código para gerar uma figura de publicação, por exemplo, ou voltar a algo que você guardou meses atrás, talvez para incorporar comentários de revisores.
afaulconbridge

Para alguém que quer ler mais, isso também é conhecido como programação alfabetizada.
LLRs

6

As principais respostas já são boas, mas eu queria abordar algumas de suas perguntas diretamente.

O teste de unidade é necessário para escrever pequenos pedaços de código?

O tamanho do código não está diretamente relacionado à necessidade de testes de unidade. Está relacionado indiretamente: testes de unidade são mais valiosos em bases de código complexas , e pequenas bases de código geralmente não são tão complexas quanto as maiores.

Os testes de unidade brilham no código, onde é fácil cometer erros ou quando você tem muitas implementações desse código. Os testes de unidade fazem pouco para ajudá-lo no desenvolvimento atual , mas fazem muito para impedir que você cometa erros no futuro que fazem com que o código existente se comporte de repente (embora você não tenha tocado nessa coisa).

Digamos que você tenha um aplicativo em que a Biblioteca A execute o quadrado dos números e a Biblioteca B aplique o teorema de Pitágoras. Obviamente, B depende de A. Você precisa consertar algo na biblioteca A e digamos que introduza um bug que cube números em vez de esquadrá-los.

De repente, a Biblioteca B começará a se comportar mal, possivelmente lançando exceções ou simplesmente fornecendo resultados incorretos. E quando você olha para o histórico de versões da biblioteca B, vê que ele está intocado. O resultado final problemático é que você não tem indicação do que poderia estar errado e precisará depurar o comportamento de B antes de perceber que o problema está em A. Isso é esforço desperdiçado.

Digite testes de unidade. Esses testes confirmam que a biblioteca A está funcionando conforme o esperado. Se você introduzir um bug na biblioteca A que faz com que ele retorne resultados ruins, seus testes de unidade perceberão isso. Portanto, você não ficará preso tentando depurar a biblioteca B.
Isso está além do seu escopo, mas em um desenvolvimento contínuo de integração, os testes de unidade são executados sempre que alguém confirma algum código, o que significa que você saberá que quebrou algo o mais rápido possível.

Especialmente para operações matemáticas complicadas, os testes de unidade podem ser uma bênção. Você faz alguns exemplos de cálculos e depois escreve testes de unidade que compararam sua saída calculada e sua saída real (com base nos mesmos parâmetros de entrada).

No entanto, observe que os testes de unidade não ajudarão a criar um bom código, mas a mantê- lo. Se você costuma escrever o código uma vez e nunca o revisitar, os testes de unidade serão menos benéficos.

E o POO?

OOP é uma maneira de pensar sobre entidades distintas, por exemplo:

Quando um Customerquer comprar um Product, ele fala com o Vendorpara receber um Order. O Accountantpagará então o Vendor.

Compare isso com a maneira como um programador funcional pensa sobre as coisas:

Quando um cliente quer purchaseProduct(), ele talktoVendor()então eles vão sendOrder()para ele. O contador irá então payVendor().

Maçãs e laranjas. Nenhum deles é objetivamente melhor que o outro. Uma coisa interessante a se notar é que, para OOP, Vendoré mencionado duas vezes, mas se refere à mesma coisa. No entanto, para programação funcional, talktoVendor()e payVendor()são duas coisas separadas.
Isso mostra a diferença entre as abordagens. Se houver muita lógica compartilhada específica do fornecedor entre essas duas ações, o OOP ajudará a reduzir a duplicação de código. No entanto, se não houver lógica compartilhada entre os dois, fundi-los em um único Vendoré um trabalho fútil (e, portanto, a programação funcional é mais eficiente).

Frequentemente, os cálculos matemáticos e científicos são operações distintas que não dependem de lógicas / fórmulas compartilhadas implícitas. Por isso, a programação funcional é mais frequentemente usada que o OOP.

Que tipos de abordagens são boas para escrever códigos bons e limpos rapidamente ao fazer "programação científica" em vez de trabalhar em projetos maiores?

Sua pergunta implica que a definição de "código bom e limpo" muda se você está fazendo programação científica ou trabalhando em projetos maiores (presumo que você queira dizer empresa).

A definição de bom código não muda. A necessidade de evitar a complexidade (o que pode ser feito escrevendo código limpo), no entanto, muda.

O mesmo argumento volta aqui.

  • Se você nunca revisitar o código antigo e entender completamente a lógica sem precisar compartimentá-lo, não faça esforços excessivos para tornar as coisas fáceis de manter.
  • Se você revisitar o código antigo, ou a lógica necessária for muito complexa para você enfrentar de uma só vez (exigindo que você compartimentar as soluções), concentre-se em escrever um fechamento limpo e reutilizável.

Eu faço essas perguntas porque, muitas vezes, a programação em si não é super complexa. É mais sobre matemática ou ciências que estou testando ou pesquisando com a programação.

Eu entendo a distinção que você está fazendo aqui, mas quando você olha para o código existente, está olhando tanto para a matemática quanto para a programação. Se um for artificial ou complexo, você terá dificuldade para lê-lo.

Por exemplo, uma classe é necessária quando duas variáveis ​​e uma função provavelmente poderiam cuidar disso?

Princípios de POO à parte, a principal razão pela qual eu escrevo classes para abrigar alguns valores de dados é porque simplifica a declaração de parâmetros do método e retorna valores. Por exemplo, se eu tenho muitos métodos que usam um local (par lat / lon), rapidamente me cansarei de digitar float latitude, float longitudee preferirei escrever Location loc.

Isso se agrava ainda mais quando você considera que os métodos geralmente retornam um valor (a menos que existam recursos específicos do idioma para retornar mais valores), e coisas como um local desejam que você retorne dois valores (lat + lon). Isso incentiva você a criar uma Locationclasse para simplificar seu código.

Por exemplo, uma classe é necessária quando duas variáveis ​​e uma função provavelmente poderiam cuidar disso?

Outro aspecto interessante a ser observado é que você pode usar o OOP sem misturar valores e métodos de dados. Nem todo desenvolvedor concorda aqui (alguns chamam de antipadrão), mas você pode ter modelos de dados anêmicos nos quais existem classes de dados separadas (armazena campos de valor) e classes lógicas (métodos de armazenagem).
É claro que isso está em um espectro. Você não precisa ser perfeitamente anêmico, você pode usá-lo quando considerar apropriado.

Por exemplo, um método que simplesmente concatena o nome e o sobrenome de uma pessoa ainda pode ser alojado na Personprópria classe, porque não é realmente "lógica", mas um valor calculado.

(Considere que essas também são geralmente situações em que a velocidade do programa é preferível no final mais rápido - quando você está executando mais de 25.000.000 etapas de tempo de uma simulação, você meio que quer que seja.)

Uma classe é sempre tão grande quanto a soma de seus campos. Tomando o exemplo de Locationnovo, que consiste em dois floatvalores, é importante observar aqui que um único Locationobjeto ocupará tanta memória quanto dois floatvalores separados .

Nesse sentido, não importa se você está usando OOP ou não. A pegada de memória é a mesma.

O desempenho em si também não é um grande obstáculo a atravessar. A diferença entre, por exemplo, usar um método global ou um método de classe não tem nada a ver com o desempenho do tempo de execução, mas tem tudo a ver com a geração de bytecode em tempo de compilação.

Pense da seguinte maneira: se eu escrevo minha receita de bolo em inglês ou espanhol não muda o fato de que o bolo levará 30 minutos para assar (= desempenho em tempo de execução). A única coisa que o idioma da receita muda é como o cozinheiro mistura os ingredientes (= compilando o bytecode).

Para o Python especificamente, você não precisa pré-compilar explicitamente o código antes de chamá-lo. No entanto, quando você não pré-compila, a compilação ocorre ao tentar executar o código. Quando digo "tempo de execução", quero dizer a própria execução, não a compilação que poderia preceder a execução.


6

Benefícios do código científico limpo

  • ... olhando livros de programação, eles geralmente parecem ser abordados em projetos maiores.

  • ... realmente faz sentido escrever código, digamos OOP, quando algumas coisas padrão poderiam ter sido feitas, teriam sido muito mais rápidas de escrever e teriam um nível de legibilidade semelhante devido à falta de programa?

Pode ser útil considerar seu código da perspectiva de um futuro codificador.

  • Por que eles abriram este arquivo?
  • O que eles estão procurando?

Da minha experiência,

O código limpo deve facilitar a verificação dos resultados

  • Facilite aos usuários saber exatamente o que eles precisam fazer para executar seu programa.
  • Você pode dividir seu programa para que algoritmos individuais possam ser comparados separadamente.

  • Evite escrever funções com efeitos colaterais contra-intuitivos, onde uma operação não relacionada faz com que outra operação se comporte de maneira diferente. Se você não puder evitá-lo, documente o que seu código precisa e como configurá-lo.

Código limpo pode servir como exemplo de código para futuros codificadores

Comentários claros (incluindo aqueles que mostram como as funções devem ser chamadas) e funções bem separadas podem fazer uma enorme diferença no tempo que leva para alguém que está começando (ou para o futuro) fazer algo útil no seu trabalho.

Além disso, criar uma "API" real para o seu algoritmo pode torná-lo melhor preparado se você decidir transformar seus scripts em uma biblioteca real para outra pessoa usar.

Recomendações

"Cite" fórmulas matemáticas usando comentários.

  • Adicione comentários para "citar" fórmulas matemáticas, especialmente se você tiver usado otimizações (identidades trigonométricas, séries Taylor, etc.).
  • Se você recebeu a fórmula do livro, adicione um comentário dizendo John Smith Method from Some Book 1st Ed. Section 1.2.3 Pg 180: se você encontrou a fórmula em um site ou jornal, cite-a também.
  • Eu recomendo evitar comentários "link apenas", certifique-se de consultar o método pelo nome em algum lugar para permitir que as pessoas o pesquisem no google, encontrei alguns comentários "link only" que redirecionaram para páginas internas antigas e podem ser muito frustrantes .
  • Você pode tentar digitar a fórmula no seu comentário se ainda for fácil de ler em Unicode / ASCII, mas isso pode ficar muito estranho (os comentários de código não são o LaTeX).

Use os comentários com sabedoria

Se você pode melhorar a legibilidade do seu código usando bons nomes de variáveis ​​/ nomes de funções, faça isso primeiro. Lembre-se de que os comentários permanecerão eternos até que você os remova. Tente fazer comentários que não desatualizem.

Use nomes descritivos de variáveis

  • Variáveis ​​de letra única podem ser a melhor opção se fizerem parte de uma fórmula.
  • Pode ser crucial que os futuros leitores possam analisar o código que você escreveu e compará-lo com a equação que está implementando.
  • Quando apropriado, considere adicionar um sufixo para descrever seu significado real, por exemplo ,. xBar_AverageVelocity
  • Como mencionado anteriormente, recomendo indicar claramente a fórmula / método que você está usando pelo nome em um comentário em algum lugar.

Escreva um código para executar seu programa contra dados bons e ruins conhecidos.

O teste de unidade é necessário para escrever pequenos pedaços de código?

Acho que o teste de unidade pode ser útil, acho que a melhor forma de teste de unidade para código científico é uma série de testes executados com dados ruins e bons conhecidos.

Escreva um código para executar seu algoritmo e verifique até que ponto o resultado se desvia do que você espera. Isso o ajudará a encontrar (potencialmente muito ruim e difícil de encontrar) problemas em que você acidentalmente codifica algo que está causando um resultado positivo falso ou comete um erro que faz com que a função sempre retorne o mesmo valor.

Observe que isso pode ser feito em qualquer nível de abstração. Por exemplo, você pode testar um algoritmo inteiro de correspondência de padrões ou uma função que apenas calcula a distância entre dois resultados no seu processo de otimização. Comece primeiro pelas áreas mais cruciais para seus resultados e / ou pelas partes do código com as quais você está mais preocupado.

Facilite a adição de novos casos de teste, considere adicionar funções "auxiliares" e estruture seus dados de entrada com eficiência. Isso pode significar salvar os dados de entrada em um arquivo para que você possa executar facilmente novamente os testes, embora tenha muito cuidado para evitar falsos positivos ou casos de teste tendenciosos / resolvidos trivialmente.

Considere usar algo como validação cruzada , consulte esta postagem na validação cruzada para obter mais informações.

Usar controle de versão

Eu recomendaria usar o controle de versão e hospedar seu repositório em um site externo. Existem sites que hospedam repositórios gratuitamente.

Vantagens:

  1. Ele fornece um backup caso seu disco rígido falhe
  2. Ele fornece um histórico, o que evita que você se preocupe se um problema recente surgido foi causado por uma alteração acidental de um arquivo, entre outros benefícios.
  3. Ele permite que você use a ramificação, que é uma boa maneira de trabalhar em códigos experimentais / de longo prazo sem afetar o trabalho não relacionado.

Tenha cuidado ao copiar / colar código

O estilo do meu código é complicado e é preenchido com comentários obsoletos, notando maneiras alternativas de fazer algo ou com linhas de código copiadas.

  • O código de copiar / colar pode economizar seu tempo, mas é uma das coisas mais perigosas que você pode fazer, especialmente se for um código que você não escreveu por si mesmo (por exemplo, se for um código de um colega).

  • Assim que o código estiver funcionando e testado, eu recomendo analisá-lo com muito cuidado para renomear quaisquer variáveis ​​ou comentar qualquer coisa que você não entenda.



6

As ferramentas do comércio geralmente são inventadas para resolver uma necessidade. Se você precisar usar a ferramenta, se não, provavelmente não precisará.

Especificamente, os programas científicos não são o objetivo final, são os meios. Você escreve o programa para resolver um problema que tem agora - não espera que o programa seja usado por outras pessoas (e precise ser mantido) em dez anos. Isso por si só significa que você não precisa pensar em nenhuma das ferramentas que permitem ao desenvolvedor atual registrar histórico para outros, como controle de versão, ou capturar funcionalidades no código, como testes de unidade.

O que beneficiaria você então?

  • O controle de versão é bom porque permite fazer backup de seu trabalho com muita facilidade. A partir de 2018, o github é um local muito popular para fazer isso (e você sempre pode movê-lo mais tarde, se necessário - o git é muito flexível). Um substituto barato e simples para backups são os procedimentos automáticos de backup em seu sistema operacional (Time Machine para Mac, rsync para Linux etc.). Seu código precisa estar em vários lugares!
  • Os testes de unidade são bons porque, se você os escrever primeiro, será forçado a pensar em como verificar o que o código realmente faz, o que ajuda a criar uma API mais útil para o seu código. Isso é útil se você escrever um código para ser reutilizado mais tarde e ajudar na alteração de um algoritmo, porque você sabe que funciona nesses casos.
  • Documentação. Aprenda a escrever a documentação adequada na linguagem de programação usada (javadoc for Java, por exemplo). Escreva para o futuro você. Nesse processo, você descobrirá que bons nomes de variáveis ​​facilitam a documentação. Iterar. Dê a mesma quantidade de cuidado à sua documentação que um poeta faz com os poemas.
  • Use boas ferramentas. Encontre um IDE que o ajude e aprenda-o bem. Refatorar como renomear variáveis ​​para um nome melhor é muito mais fácil dessa maneira.
  • Se você tem colegas, considere usar a revisão por pares. Ter uma visão externa e entender seu código é a versão aqui e agora do futuro para o qual você escreve. Se o seu colega não entender o seu código, você provavelmente também não o entenderá mais tarde.

Como esta resposta não recebeu um voto positivo? Tem agora. Nosso grupo considerou a revisão por pares uma das ferramentas mais eficazes de todas, muito mais importante do que os testes de unidade no que diz respeito ao código científico. É fácil cometer um erro ao converter um conjunto complexo de equações em um artigo de revista científica para codificar. Cientistas e engenheiros costumam criar programadores extremamente pobres; a revisão por pares pode captar feiúra arquitetônica que dificulta a manutenção / compreensão / uso do código.
David Hammen

5

Além dos bons conselhos já aqui, você pode considerar o objetivo de sua programação e, portanto, o que é importante para você.

"É mais sobre matemática ou ciências que estou testando ou pesquisando com a programação".

Se o objetivo é experimentar e testar algo para seu próprio entendimento e você souber quais devem ser os resultados, seu código é basicamente um desperdício rápido e sua abordagem atual pode ser suficiente, embora possa ser melhorada. Se os resultados não forem os esperados, você poderá voltar e revisar.

No entanto, se os resultados da sua codificação estão informando a direção de sua pesquisa e você não sabe quais devem ser os resultados , a correção se torna particularmente importante. Um erro no seu código pode levar você a tirar conclusões erradas do seu experimento, com várias implicações ruins para sua pesquisa geral.

Nesse caso, dividir seu código em funções facilmente compreensíveis e verificáveis ​​com testes de unidade fornecerá tijolos de construção mais sólidos, dando a você mais confiança em seus resultados e poderá evitar muitas frustrações posteriormente.


5

Por mais que o controle de versão e o teste de unidade sejam para manter seu código geral organizado e funcional, nenhum dos dois ajuda a escrever um código mais limpo.

  • O controle de versão permitirá que você veja como e quando o código ficou tão confuso quanto é.
  • Os testes de unidade garantirão que, apesar do código ser uma bagunça completa, ele ainda funcione.

Se você deseja impedir-se de escrever código desarrumado, precisa de uma ferramenta que funcione onde as bagunças acontecem: quando você estiver escrevendo o código. Um tipo popular de ferramenta que faz isso é chamado de linter. Não sou desenvolvedor de python, mas parece que o Pylint pode ser uma boa opção.

Um linter analisa o código que você escreveu e o compara a um conjunto configurável de práticas recomendadas. Se o linter tiver uma regra em que as variáveis ​​devem ser camelCasee você escrever uma snake_case, isso sinalizará isso como um erro. Bons linters têm regras que variam de "variáveis ​​declaradas devem ser usadas" a "A complexidade ciclomática de funções deve ser menor que 3".

A maioria dos editores de código pode ser configurada para executar um linter toda vez que você salva, ou apenas geralmente enquanto digita, e indica problemas em linha. Se você digitar algo como x = 7, xserá destacado, com uma instrução para usar um nome melhor e mais longo (se é isso que você configurou). Isso funciona como verificação ortográfica na maioria dos processadores de texto, dificultando a ignição e ajudando a criar melhores hábitos.


Isso deve ter muito mais votos positivos. +1
heather

2
Mas, pelo amor de Deus, certifique-se de saber como configurar o linter para um estilo que você goste, caso contrário, isso o deixará louco com essa agitação.
DrMcCleod

4

Tudo o que você listou é uma ferramenta na caixa de ferramentas metafórica. Como tudo na vida, ferramentas diferentes são apropriadas para tarefas diferentes.

Comparado a outros campos de engenharia, o software trabalha com várias partes individuais que, por si só, são bastante simples. Uma declaração de atribuição não avalia de forma diferente, dependendo das flutuações de temperatura da sala. Uma ifdeclaração não corroer no lugar e continuar retornando a mesma coisa depois de um tempo. Porém, como os elementos individuais são muito simples e o software é criado por humanos, esses elementos são combinados em partes cada vez maiores até que o resultado se torne tão grande e complexo que atinge os limites do que as pessoas podem gerenciar mentalmente.

À medida que os projetos de software cresceram e cresceram, as pessoas os estudaram e criaram ferramentas para tentar gerenciar essa complexidade. OOP é um exemplo. Cada vez mais linguagens de programação abstratas são outros meios. Porque muito do dinheiro em software está fazendo mais e mais , são necessárias ferramentas para alcançar isso, sobre as quais você verá e lerá. Mas parece que essas situações não se aplicam a você.

Portanto, não sinta que precisa fazer nada disso. No final do dia, o código é apenas um meio para atingir um fim. Infelizmente, o que melhor lhe dará a perspectiva certa sobre o que é e o que não é apropriado é trabalhar em alguns projetos maiores, pois é muito mais difícil saber o que está faltando quando a caixa de ferramentas está em sua mente.

De qualquer forma, não me preocuparia em não usar OOP ou outras técnicas, desde que seus scripts sejam pequenos. Muitos dos problemas que você descreveu são apenas habilidades organizacionais profissionais gerais, ou seja, não perder um arquivo antigo é algo com o qual todos os campos precisam lidar.


4

Além de todas as boas sugestões fornecidas até agora, uma prática que aprendi ao longo do tempo e que considero essencial é adicionar muito liberalmente comentários detalhados ao seu código. É a coisa mais importante para mim quando volto a algo depois de um longo lapso de tempo. Explique para si mesmo o que você está pensando. Demora um pouco, mas é relativamente fácil e praticamente indolor.

Às vezes, tenho duas ou três vezes mais linhas de comentários do que o código, especialmente quando os conceitos ou técnicas são novos para mim e justifico a minha explicação.

Faça o controle de versão, aprimore suas práticas, etc. .... todas as opções acima. Mas explique as coisas para si mesmo à medida que avança. Funciona muito bem.


4

Que qualidades são importantes para esse tipo de programa?

Provavelmente não importa se é fácil mantê-lo ou evoluí-lo, porque as chances são de que isso não vai acontecer.

Provavelmente não importa o quão eficiente seja.

Provavelmente não importa se possui uma ótima interface de usuário ou se é segura contra invasores mal-intencionados.

Pode ser que seja legível: que alguém que esteja lendo seu código possa facilmente se convencer de que faz o que afirma fazer.

Certamente importa que esteja correto. Se o programa der resultados incorretos, essas são as suas conclusões científicas imediatamente. Mas ele só precisa processar corretamente a entrada que você está realmente pedindo; realmente não importa muito se ele cai se receber valores de dados de entrada negativos, se todos os seus valores de dados forem positivos.

Também é importante que você mantenha algum nível de controle de alterações. Seus resultados científicos precisam ser reproduzíveis, e isso significa que você precisa saber qual versão do programa produziu os resultados que pretende publicar. Como existe apenas um desenvolvedor, o controle de alterações não precisa ser muito elaborado, mas você precisa ter certeza de que pode voltar a um ponto no tempo e reproduzir seus resultados.

Portanto, não se preocupe com paradigmas de programação, orientação a objetos, elegância algorítmica. Preocupe-se com a clareza, a legibilidade e a rastreabilidade de suas alterações ao longo do tempo. Não se preocupe com a interface do usuário. Não se preocupe em testar todas as combinações possíveis de parâmetros de entrada, mas faça testes suficientes para ter certeza (e para tornar os outros confiantes) de que seus resultados e conclusões são válidos.


4

Eu trabalhei em um ambiente semelhante com acadêmicos que escrevem muito código (matemática / ciências), mas o progresso deles é lento devido aos mesmos motivos que você descreve. No entanto, notei uma coisa específica que correu bem e acho que também pode ajudá-lo: criar e manter uma coleção de bibliotecas especializadas que podem ser usadas em vários projetos. Essas bibliotecas devem fornecer funções utilitárias e, portanto, ajudarão a manter seu projeto atual específico para o domínio do problema.

Por exemplo, você pode ter que lidar com muitas transformações coordenadas em seu campo (ECEF, NED, lat / lon, WGS84 etc.), portanto, uma função como convert_ecef_to_ned()deve entrar em um novo projeto chamado CoordinateTransformations. Coloque o projeto sob controle de versão e hospede-o nos servidores do seu departamento para que outras pessoas possam usá-lo (e, esperançosamente, melhorá-lo). Depois de alguns anos, você deverá ter uma coleção robusta de bibliotecas com seus projetos contendo apenas código específico para um domínio de pesquisa / problema específico.

Alguns conselhos mais gerais:

  • Sempre tente modelar seu problema específico com a maior precisão possível, não importa qual seja. Dessa forma, as questões de design de software, como o que / onde / como colocar uma variável, devem se tornar muito mais óbvias para responder.
  • Eu não me incomodaria com o desenvolvimento orientado a testes, pois o código científico descreve idéias e conceitos e é mais criativo e fluido; não há APIs para definir, serviços para manter, riscos para o código de outras pessoas ao alterar a funcionalidade etc.

Não deixe que outras pessoas o aprimorem. As chances são de que eles não entendam o propósito do código e irão estragar tudo.
mathreadler

@mathreadler Bem, se são bibliotecas de utilidade geral, será um pouco difícil para outras pessoas estragar tudo, essa é a ideia.
Jigglypuff

Por que é difícil estragar as bibliotecas de uso geral? Não é tão difícil se você não tem idéia do que está fazendo, ou se se esforça muito, também.
Mathreadler

@mathreadler Porque geralmente há apenas uma maneira de coordenar transformações ou conversões de unidades, por exemplo.
jigglypuff

Geralmente, existem várias maneiras, dependendo de como seus números são armazenados na memória, qual representação eles usam e muitas outras coisas, para qual CPU você pretende compilar a biblioteca. Um codificador pode assumir que todos sempre usarão IEEE duplos, por exemplo, mas outro quase sempre usa precisão única ou algum terceiro formato mais estranho. Um codificador então usará o polimorfismo de gabarito, mas outro pode ser alérgico a ele; outro terceiro, ainda mais estranho, será codificar tudo em baixo nível c ou montagem.
mathreadler

3

A seguir, minhas opiniões e muito influenciadas por meu próprio caminho particular.

A codificação geralmente gera perspectivas dogmáticas sobre como você deve fazer as coisas. Em vez de técnicas e ferramentas, acho que você precisa examinar os valores e custos cumulativos para decidir sobre uma estratégia apropriada.

Escrever código bom, legível, depurável e sólido leva muito tempo e esforço. Em muitos casos, dado um horizonte de planejamento limitado, não vale a pena fazer isso (paralisia da análise).

Um colega tinha uma regra de ouro; se você estiver fazendo o mesmo tipo de coisa pela terceira vez, invista esforços, caso contrário, um trabalho rápido e sujo é apropriado.

Testes de algum tipo são essenciais, mas, para projetos pontuais, simplesmente observar os olhos pode ser suficiente. Para qualquer coisa substancial, os testes e a infraestrutura de teste são essenciais. O valor é que você libera ao codificar, o custo é que, se o teste se concentrar em uma implementação específica, os testes também precisam de manutenção. Os testes também lembram como as coisas devem funcionar.

Para meus próprios scripts únicos (geralmente para validar uma estimativa de probabilidade ou similar), achei duas pequenas coisas muito úteis: 1. Inclua um comentário mostrando como o código é usado. 2. Inclua uma breve descrição do motivo pelo qual você escreveu o código. Essas coisas são terrivelmente óbvias quando você escreve o código, mas a obviedade desperdiça com o tempo :-).

OOP é sobre reutilizar código, abstrair, encapsular, fatorar, etc. Muito útil, mas fácil de se perder se a produção de código e design de qualidade não for seu objetivo final. Leva tempo e esforço para produzir coisas de qualidade.


3

Embora eu pense que os testes de unidade têm seus méritos, eles são de valor duvidoso para o desenvolvimento científico - geralmente são pequenos demais para oferecer muito valor.

Mas eu realmente gosto de testes de integração para código científico:

Isole um pequeno pedaço do seu código que possa funcionar por si só, por exemplo, o pipeline ETL. Em seguida, escreva um teste que forneça os dados, execute o pipeline etl (ou apenas uma etapa) e depois teste se o resultado corresponde às suas expectativas. Embora o pedaço testado possa ter muito código, o teste ainda fornece valor:

  1. Você tem um gancho conveniente para executar novamente seu código, o que ajuda a executá-lo com frequência.
  2. Você pode testar algumas suposições no seu teste
  3. Se alguém pensar, é fácil adicionar um teste com falha e fazer a correção
  4. Você codifica as entradas / saídas esperadas, evitando a dor de cabeça usual resultante de tentar adivinhar o formato dos dados de entrada.
  5. Embora não sejam tão enxutos quanto os testes de unidade, os testes de TI ainda ajudam a separar seu código e forçam você a adicionar alguns limites ao seu código.

Eu estou usando essa técnica frequentemente, e muitas vezes acabo com uma função principal legível de maneira relativizada, mas as sub-funções são geralmente muito longas e feias, mas podem ser modificadas e reorganizadas rapidamente devido a limites de E / S robustos.


2

Eu normalmente trabalho em uma base de fontes muito grande. Usamos todas as ferramentas que você mencionou. Recentemente, comecei a trabalhar em alguns scripts python para um projeto paralelo. São de algumas dezenas a algumas centenas de linhas, no máximo. Por hábito, comprometi meus scripts no controle de origem. Isso foi útil porque posso criar ramificações para experimentar experimentos que podem não funcionar. Posso bifurcar se precisar duplicar o código e modificá-lo para outra finalidade. Isso deixa o original intacto, caso eu precise trazê-lo novamente.

Para "testes de unidade", apenas tenho alguns arquivos de entrada destinados a produzir alguma saída conhecida que verifico manualmente. Provavelmente eu poderia automatizar, mas parece que levaria mais tempo para fazer isso do que eu economizaria. Provavelmente depende de quantas vezes eu tenho que modificar e executar os scripts. De qualquer forma, se funcionar, faça. Se houver mais problemas do que vale a pena, não perca seu tempo.


2

Com a escrita de código - como com a escrita em geral - a questão principal é:

Qual leitor você tem em mente? ou Quem consome seu código?

Coisas como diretrizes formais de codificação não fazem sentido quando você é seu único público.

Dito isto, por outro lado, seria útil escrever o código de uma maneira; no seu futuro, você será capaz de entendê-lo imediatamente.

Portanto, um "bom estilo" seria o que mais ajuda você. Como esse estilo deve ser é uma resposta que não posso dar.

Eu acho que você não precisa de testes de OOP ou de unidade para arquivos de 150 LOC. Um VCS dedicado seria interessante quando você tem algum código em evolução. Caso contrário, a .bakfaz o truque. Essas ferramentas são uma cura para uma doença, você pode até não ter.

Talvez você deva escrever seu código de tal maneira que, mesmo que você o leia enquanto está bêbado, é capaz de lê-lo, entendê-lo e modificá-lo.

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.