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 .
Respostas:
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.
fonte
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.
fonte
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ô.
fonte
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 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.
fonte
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
:ContactGeometry_Sphere.cpp
:fonte
λ
ouφ
em vez do feiolambda_
ouphy
...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:
Um esboço de alto nível das práticas que eles recomendam:
O artigo entra em detalhes consideráveis em cada um desses pontos.
fonte
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:
# 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.
fonte
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.
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.
fonte
Concordo com os outros que o controle de versão resolverá muitos dos seus problemas imediatamente. Especificamente:
Eu diria que não pense demais: basta usar o git. Atenha-se a comandos simples (por exemplo, apenas um único
master
ramo), 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:
As asserções não têm essas desvantagens, pois são verificadas durante a execução normal de um programa. Em particular:
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
-O
opçã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
):Podemos reescrevê-lo em uma "derivação" do Nix, assim, como este (por exemplo
run.nix
):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émrun.nix
). Awith import ...
linha importa a biblioteca padrão do Nix , que fornecerunCommand
a execução do código bash. Podemos executar nosso experimento usandonix-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
$HOME
ou a qualquer software de sistema que instalamos. Isso torna o resultado independente dos detalhes de nossa máquina atual, como o conteúdo~/.config
ou as versões dos programas que instalamos; Também conhecido como material que impede outras pessoas de replicar nossos resultados! Por isso acrescenteicp
comando, 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?):O mesmo
nix-build run.nix
executará 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)
fonte
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.
fonte
As principais respostas já são boas, mas eu queria abordar algumas de suas perguntas diretamente.
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.
OOP é uma maneira de pensar sobre entidades distintas, por exemplo:
Compare isso com a maneira como um programador funcional pensa sobre as coisas:
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()
epayVendor()
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.
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.
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.
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 longitude
e preferirei escreverLocation 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
Location
classe para simplificar seu código.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
Person
própria classe, porque não é realmente "lógica", mas um valor calculado.Uma classe é sempre tão grande quanto a soma de seus campos. Tomando o exemplo de
Location
novo, que consiste em doisfloat
valores, é importante observar aqui que um únicoLocation
objeto ocupará tanta memória quanto doisfloat
valores 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.
fonte
Benefícios do código científico limpo
Pode ser útil considerar seu código da perspectiva de um futuro codificador.
Da minha experiência,
O código limpo deve facilitar a verificação dos resultados
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.
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.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
xBar_AverageVelocity
Escreva um código para executar seu programa contra dados bons e ruins conhecidos.
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:
Tenha cuidado ao copiar / colar código
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.
fonte
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?
fonte
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.
fonte
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.
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
camelCase
e você escrever umasnake_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
,x
será 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.fonte
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
if
declaraçã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.
fonte
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.
fonte
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.
fonte
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 chamadoCoordinateTransformations
. 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:
fonte
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.
fonte
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:
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.
fonte
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.
fonte
Com a escrita de código - como com a escrita em geral - a questão principal é:
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
.bak
faz 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.
fonte