web-dev-qa-db-fra.com

Programmation propre lors de l'écriture de code scientifique

Je n'écris pas vraiment de gros projets. Je ne gère pas une énorme base de données ni ne traite de millions de lignes de code.

Mon code est principalement des trucs de type "script" - des choses pour tester des fonctions mathématiques, ou pour simuler quelque chose - "une programmation scientifique". Les programmes les plus longs sur lesquels j'ai travaillé jusqu'à présent sont quelques centaines de lignes de code, et la plupart des programmes sur lesquels je travaille sont environ 150.

Mon code est aussi de la merde. Je m'en suis rendu compte l'autre jour alors que j'essayais de trouver un fichier que j'avais écrit il y a quelque temps mais que j'avais probablement écrasé et que je n'utilisais pas le contrôle de version, ce qui fait probablement grincer des dents à un grand nombre d'entre vous à cause de ma stupidité.

Le style de mon code est compliqué et est rempli de commentaires obsolètes notant d'autres façons de faire quelque chose ou de lignes de code copiées. Alors que les noms de variables commencent toujours très bien et sont descriptifs, lorsque j'ajoute ou change des choses comme par exemple, quelque chose de nouveau que quelqu'un veut tester, le code est superposé et écrasé et parce que je pense que cette chose devrait être testée rapidement maintenant que je avoir un cadre, je commence à utiliser des noms de variables merdiques et le fichier va au pot.

Dans le projet sur lequel je travaille maintenant, je suis dans la phase où tout cela revient me mordre énormément. Mais le problème est (en dehors de l'utilisation du contrôle de version, de la création d'un nouveau fichier pour chaque nouvelle itération et de l'enregistrement de tout cela dans un fichier texte quelque part, ce qui aidera probablement la situation de manière spectaculaire) Je ne sais pas vraiment comment procéder pour améliorer mon style de codage réel.

Des tests unitaires sont-ils nécessaires pour écrire de petits morceaux de code? Et la POO? Quelles sortes d'approches sont bonnes pour écrire rapidement du bon code propre lors de la "programmation scientifique" plutôt que de travailler sur des projets plus importants?

Je pose ces questions car souvent, la programmation elle-même n'est pas super complexe. C'est plus sur les mathématiques ou les sciences que je teste ou recherche avec la programmation. Par exemple, une classe est-elle nécessaire lorsque deux variables et une fonction pourraient probablement s'en occuper? (Considérez que ce sont généralement des situations où la vitesse du programme est préférée pour être plus rapide - lorsque vous exécutez plus de 25 000 000 pas de temps d'une simulation, vous voulez un peu qu'elle soit.)

Peut-être que c'est trop large, et si c'est le cas, je m'excuse, mais en regardant les livres de programmation, ils semblent souvent être abordés dans des projets plus importants. Mon code n'a pas besoin POO, et il est déjà sacrément court donc ce n'est pas comme "oh, mais le fichier sera réduit de mille lignes si nous le faisons! " Je veux savoir comment "recommencer" et programmer proprement sur ces projets plus petits et plus rapides.

Je serais heureux de fournir des détails plus précis, mais plus les conseils sont généraux, plus je pense qu'ils sont utiles. Je programme en Python 3.


Quelqu'un a suggéré un doublon. Permettez-moi de préciser que je ne parle pas d'ignorer purement et simplement les normes de programmation standard. De toute évidence, il y a une raison pour laquelle ces normes existent. Mais d'un autre côté, est-il vraiment logique d'écrire du code, c'est-à-dire OOP lorsque des choses standard auraient pu être faites, auraient été beaucoup plus rapides à écrire et auraient été similaires niveau de lisibilité en raison de la brièveté du programme?

Il y a des exceptions. De plus, il existe probablement des normes pour la programmation scientifique au-delà de simples normes. Je pose des questions à ce sujet également. Il ne s'agit pas de savoir si les normes de codage normales doivent être ignorées lors de l'écriture de code scientifique, il s'agit d'écrire du code scientifique propre!


Mise à jour

Je pensais juste ajouter une sorte de mise à jour "pas tout à fait une semaine plus tard". Tous vos conseils ont été extrêmement utiles. J'utilise maintenant le contrôle de version - git, avec git kraken pour une interface graphique. Il est très facile à utiliser et a nettoyé mes fichiers de manière drastique - plus besoin d'anciens fichiers qui restent, ou d'anciennes versions de code commentées "au cas où".

J'ai également installé pylint et l'ai exécuté sur tout mon code. Un fichier a obtenu un score négatif au départ; Je ne sais même pas comment cela a été possible. Mon fichier principal a commencé à un score de ~ 1,83/10 et est maintenant à ~ 9,1/10. Tout le code se conforme désormais assez bien aux normes. Je l'ai également parcouru de mes propres yeux en mettant à jour les noms de variables qui avaient mal tourné ... euh ... et en cherchant des sections à refactoriser.

En particulier, j'ai posé une question récente sur ce site sur la refactorisation d'une de mes fonctions principales, et elle est désormais beaucoup plus propre et beaucoup plus courte: au lieu d'une fonction longue, gonflée, si/sinon remplie, elle est désormais inférieure à la moitié la taille et beaucoup plus facile de comprendre ce qui se passe.

Ma prochaine étape consiste à mettre en œuvre des "tests unitaires" en quelque sorte. J'entends par là un fichier que je peux exécuter sur mon fichier principal qui examine toutes les fonctions qu'il contient avec des instructions assert et try/excepts, ce qui n'est probablement pas la meilleure façon de le faire, et entraîne beaucoup de code en double, mais je vais continuer à lire et essayer de trouver comment mieux faire.

J'ai également mis à jour de manière significative la documentation que j'avais déjà écrite et ajouté des fichiers supplémentaires comme une feuille de calcul Excel, la documentation et un papier associé au référentiel github. Cela ressemble à un vrai projet de programmation maintenant.

Alors ... je suppose que c'est tout pour dire: merci.

169
heather

C'est un problème assez courant pour les scientifiques. Je l'ai beaucoup vu, et cela découle toujours du fait que la programmation est quelque chose que vous choisissez en tant qu'outil pour faire votre travail.

Vos scripts sont donc un gâchis. Je vais aller à l'encontre du bon sens et dire que, en supposant que vous programmez seul, ce n'est pas si mal! Vous n'allez plus jamais toucher à la plupart de ce que vous écrivez, donc dépenser trop de temps pour écrire du joli code au lieu de produire de la "valeur" (donc le résultat de votre script) ne vous fera pas grand-chose.

Cependant, il y aura un moment où vous devrez revenir à quelque chose que vous avez fait et voir exactement comment quelque chose fonctionnait. De plus, si d'autres scientifiques doivent revoir votre code, il est très important qu'il soit aussi clair et concis que possible, afin que tout le monde puisse le comprendre.

Votre principal problème va être la lisibilité, alors voici quelques conseils pour vous améliorer:

Noms de variables:

Les scientifiques aiment utiliser des notations concises. Toutes les équations mathématiques utilisent généralement des lettres simples comme variables, et je ne serais pas surpris de voir beaucoup, beaucoup de variables très courtes dans votre code. Cela nuit beaucoup à la lisibilité. Lorsque vous reviendrez à votre code, vous ne vous souviendrez pas de ce que ces y, i et x2 représentent, et vous passerez beaucoup de temps à essayer de le comprendre. Essayez plutôt de nommer explicitement vos variables, en utilisant des noms qui représentent exactement ce qu'elles sont.

Divisez votre code en fonctions:

Maintenant que vous avez renommé toutes vos variables, vos équations sont terribles et comportent plusieurs lignes.

Au lieu de la laisser dans votre programme principal, déplacez cette équation vers une fonction différente et nommez-la en conséquence. Maintenant, au lieu d'avoir une ligne de code énorme et foirée, vous aurez de courtes instructions vous indiquant exactement ce qui se passe et quelle équation vous avez utilisée. Cela améliore à la fois votre programme principal, car vous n'avez même pas besoin de regarder l'équation réelle pour savoir ce que vous avez fait, et le code de l'équation lui-même, car dans une fonction séparée, vous pouvez nommer vos variables comme vous le souhaitez et revenir à les lettres simples les plus familières.

Sur cette ligne de pensée, essayez de trouver tous les morceaux de code qui représentent quelque chose, surtout si ce quelque chose est quelque chose que vous devez faire plusieurs fois dans votre code, et les diviser en fonctions. Vous découvrirez que votre code deviendra rapidement plus facile à lire et que vous pourrez utiliser les mêmes fonctions sans écrire plus de code.

Cerise sur le gâteau, si ces fonctions sont nécessaires dans plus de vos programmes, vous pouvez simplement créer une bibliothèque pour eux, et vous les aurez toujours disponibles.

Variables globales:

À l'époque où j'étais débutant, je pensais que c'était un excellent moyen de faire circuler les données dont j'avais besoin dans de nombreux points de mon programme. Il s'avère qu'il existe de nombreuses autres façons de faire circuler les choses, et la seule chose que les variables globales font est de donner des maux de tête aux gens, car si vous allez à un point aléatoire de votre programme, vous ne saurez jamais quand cette valeur a été utilisée ou modifiée pour la dernière fois, et le retrouver sera pénible. Essayez de les éviter autant que possible.

Si vos fonctions doivent renvoyer ou modifier plusieurs valeurs, créez une classe avec ces valeurs et transmettez-les en tant que paramètre, ou faites en sorte que la fonction renvoie plusieurs valeurs (avec des tuples nommés) et attribue ces valeurs dans le code de l'appelant.

Contrôle de version

Cela n'améliore pas directement la lisibilité, mais vous aide à faire tout ce qui précède. Chaque fois que vous apportez des modifications, engagez-vous dans le contrôle de version (un référentiel Git local sera assez bien), et si quelque chose ne fonctionne pas, regardez ce que vous avez changé ou restaurez simplement! Cela facilitera la refactorisation de votre code et constituera un filet de sécurité si vous cassez accidentellement des éléments.

Garder tout cela à l'esprit vous permettra d'écrire du code clair et plus efficace, et vous aidera également à trouver les erreurs possibles plus rapidement, car vous n'aurez pas à parcourir des fonctions gigantesques et des variables désordonnées.

164
BgrWorker

Physicien ici. Été là.

Je dirais que votre problème ne concerne pas le choix des outils ou des paradigmes de programmation (tests unitaires, POO, peu importe). Il s'agit de l'attitude , de l'état d'esprit. Le fait que les noms de vos variables soient bien choisis au début et finissent par être de la merde est assez révélateur. Si vous pensez que votre code est "exécuté une fois, puis jeté", alors ce sera inévitablement un gâchis. Si vous le considérez comme le produit de l'artisanat et de l'amour, ce sera magnifique.

Je crois qu'il n'y a qu'une une recette pour écrire du code propre: écrivez-le pour l'être humain qui va le lire, pas pour l'interpréteur qui va l'exécuter. L'interprète ne se soucie pas si votre code est un gâchis, mais le lecteur humain le fait se soucie.

Tu es un scientifique. Vous pouvez probablement passer beaucoup de temps à peaufiner un article scientifique. Si votre premier projet semble compliqué, vous le refactoriserez jusqu'à ce que la logique coule juste de la manière la plus naturelle. Vous voulez que vos collègues le lisent et trouvent les arguments clairs. Vous voulez que vos élèves puissent en tirer des leçons.

Écrire du code propre est exactement identique. Considérez votre code comme une explication détaillée d'un algorithme qui, accessoirement, n'est lisible que par machine. Imaginez que vous allez le publier comme un article que les gens liront. Vous allez même le montrer lors d'une conférence et guider le public ligne par ligne. Maintenant répétez votre présentation . Oui, ligne par ligne! Embarrassant, non? Alors nettoyez vos diapositives (euh ... je veux dire, votre code), et répétez à nouveau. Répétez jusqu'à ce que vous soyez satisfait du résultat.

Ce serait encore mieux si, après les répétitions, vous pouvez montrer votre code à de vraies personnes plutôt qu'à des personnes imaginaires et à votre futur. Le parcourir ligne par ligne est appelé une "marche de code", et ce n'est pas une pratique stupide.

Bien sûr, tout cela a un coût. Écrire du code propre prend beaucoup plus de temps que d'écrire du code jetable. Vous seul pouvez évaluer si les avantages l'emportent sur le coût de votre cas d'utilisation particulier.

Quant aux outils, j'ai dit avant qu'ils n'étaient pas ça importants. Cependant, si je devais en choisir un, je dirais que le contrôle de version est le plus utile.

141
Edgar Bonet

Le contrôle de version va probablement vous en donner le plus pour votre argent. Ce n'est pas seulement pour le stockage à long terme, c'est génial pour suivre votre expérimentation à court terme et revenir à la dernière version qui a fonctionné, en gardant des notes en cours de route.

Les tests unitaires sont ensuite les plus utiles. La chose au sujet des tests unitaires est que même les bases de code avec des millions de lignes de code sont testées à l'unité une fonction à la fois. Les tests unitaires sont effectués dans le petit, au niveau d'abstraction le plus bas. Cela signifie qu'il n'y a fondamentalement aucune différence entre les tests unitaires écrits pour les petites bases de code et ceux utilisés pour les grandes. Il y en a juste plus.

Les tests unitaires sont le meilleur moyen d'éviter de casser quelque chose qui fonctionnait déjà lorsque vous corrigez autre chose, ou du moins de vous le dire rapidement lorsque vous le faites. Ils sont en fait plus utiles lorsque vous n'êtes pas un programmeur aussi qualifié, ou que vous ne savez pas comment ou que vous ne voulez pas écrire un code plus détaillé qui est structuré pour rendre les erreurs moins probables ou plus évidentes.

Entre le contrôle de version et l'écriture de tests unitaires au fur et à mesure, votre code deviendra naturellement beaucoup plus propre. D'autres techniques pour un codage plus propre peuvent être apprises lorsque vous atteignez un plateau.

81
Karl Bielefeldt

(en plus d'utiliser le contrôle de version, de créer un nouveau fichier pour chaque nouvelle itération et de l'enregistrer dans un fichier texte quelque part, ce qui aidera probablement la situation de manière spectaculaire)

Vous l'auriez probablement compris vous-même, mais si vous devez " tout enregistrer dans un fichier texte quelque part " vous n'utilisez pas la version système de contrôle à son plein potentiel. Utilisez quelque chose comme Subversion, git ou Mercurial et écrivez un bon message de validation avec chaque validation et vous aurez un journal qui sert le but du fichier texte mais ne peut pas être séparé du référentiel.

Cela mis à part, l'utilisation du contrôle de version est la chose la plus importante que vous puissiez faire pour une raison qu'aucune des réponses existantes ne mentionne: reproductibilité des résultats. Si vous pouvez utiliser vos messages de journal ou ajouter une note aux résultats avec le numéro de révision, vous pouvez être sûr de pouvoir régénérer les résultats et vous serez mieux placé pour publier le code avec le papier.

Des tests unitaires sont-ils nécessaires pour écrire de petits morceaux de code? Et la POO? Quelles sortes d'approches sont bonnes pour écrire rapidement du bon code propre lors de la "programmation scientifique" plutôt que de travailler sur des projets plus importants?

Les tests unitaires ne sont jamais nécessaires, mais ils sont utiles si (a) le code est suffisamment modulaire pour que vous puissiez tester les unités plutôt que le tout; (b) vous pouvez créer des tests. Idéalement, vous seriez en mesure d'écrire la sortie attendue à la main plutôt que de la générer avec le code, bien que la générer par le code puisse au moins vous donner des tests de régression qui vous indiquent si quelque chose a changé son comportement. Considérez simplement si les tests sont plus susceptibles d'être bogués que le code qu'ils testent.

La POO est un outil. Utilisez-le si cela aide, mais ce n'est pas le seul paradigme. Je suppose que vous ne connaissez vraiment que la programmation procédurale: si tel est le cas, dans le contexte décrit, je pense que vous bénéficieriez davantage de l'étude de la programmation fonctionnelle que de la POO, et en particulier de la discipline d'éviter les effets secondaires lorsque cela est possible. Python peut être écrit dans un style très fonctionnel.

29
Peter Taylor

À l'école d'études supérieures, j'ai écrit moi-même un code lourd d'algorithmes. C'est un peu difficile à casser. Pour le dire grossièrement, de nombreuses conventions de programmation sont construites autour de l'idée de mettre des informations dans une base de données, de les récupérer au bon moment, puis de masser ces données pour les présenter à un utilisateur, en utilisant généralement une bibliothèque pour tout calcul mathématique ou parties lourdes d'algorithmes de ce processus. Pour ces programmes, tout ce que vous avez entendu au sujet de la POO, de la décomposition du code en fonctions courtes et de rendre tout facilement compréhensible en un coup d'œil lorsque c'est possible est un excellent conseil. Mais cela ne fonctionne pas tout à fait pour le code lourd d'algorithme, ou le code qui implémente des calculs mathématiques complexes et rien d'autre.

Si vous écrivez des scripts pour effectuer des calculs scientifiques, vous avez probablement des articles avec les équations ou les algorithmes que vous utilisez écrits. Si vous utilisez vous-même de nouvelles idées que vous avez découvertes, vous allez, espérons-le, les publier dans vos propres articles. Dans ce cas, la règle est la suivante: Vous voulez que votre code ressemble autant que possible aux équations publiées. Voici une réponse sur Software Engineering.SE avec plus de 200 votes positifs plaidant pour cette approche et expliquant à quoi elle ressemble: Y a-t-il une excuse pour les noms de variables courts?

Comme autre exemple, il existe d'excellents extraits de code dans Simbody , un outil de simulation physique utilisé pour la recherche et l'ingénierie en physique. Ces extraits contiennent un commentaire montrant une équation utilisée pour un calcul, suivi d'un code qui se rapproche le plus possible des équations mises en œuvre.

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);
21
Kevin

Donc, mon travail de jour est dans la publication et la conservation des données de recherche pour le système de l'Université de Californie. Quelques personnes ont mentionné la reproductibilité, et je pense que c'est vraiment le problème principal ici: documenter votre code comme vous documenteriez tout ce dont quelqu'un a besoin pour reproduire votre expérience, et, idéalement, écrire du code qui le rend simple pour quelqu'un d'autre à la fois pour reproduire votre expérience et vérifier vos résultats pour les sources d'erreur.

Mais quelque chose que je n'ai pas vu mentionné, qui je pense est important, est que les organismes de financement envisagent de plus en plus la publication de logiciels comme une partie de la publication de données et font de la publication de logiciels une exigence pour la science ouverte.

À cette fin, si vous voulez quelque chose de spécifique, destiné aux chercheurs plutôt qu'aux développeurs de logiciels en général, je ne peux pas recommander assez l'organisation Software Carpentry . Si vous pouvez assister à l'un de leurs ateliers , parfait; si tout ce que vous avez le temps/l'accès à faire est de lire certains de leurs articles sur meilleures pratiques de calcul scientifique , c'est bien aussi. De ce dernier:

Les scientifiques développent généralement leur propre logiciel à ces fins, car cela nécessite des connaissances substantielles spécifiques au domaine. En conséquence, des études récentes ont montré que les scientifiques passent généralement 30% ou plus de leur temps à développer des logiciels. Cependant, 90% ou plus d'entre eux sont principalement autodidactes et ne sont donc pas exposés aux pratiques de développement logiciel de base telles que l'écriture de code maintenable, l'utilisation du contrôle de version et des suiveurs de problèmes, les révisions de code, les tests unitaires et l'automatisation des tâches.

Nous pensons que les logiciels ne sont qu'un autre type d'appareil expérimental et doivent être construits, vérifiés et utilisés avec autant de soin que n'importe quel appareil physique. Cependant, alors que la plupart des scientifiques veillent à valider leur équipement de laboratoire et de terrain, la plupart ne savent pas à quel point leur logiciel est fiable. Cela peut conduire à de graves erreurs affectant les conclusions centrales des recherches publiées. …

De plus, comme les logiciels sont souvent utilisés pour plus d'un projet et sont souvent réutilisés par d'autres scientifiques, les erreurs de calcul peuvent avoir des impacts disproportionnés sur le processus scientifique. Ce type d'impact en cascade a provoqué plusieurs rétractations importantes lorsqu'une erreur du code d'un autre groupe n'a été découverte qu'après la publication.

Un aperçu de haut niveau des pratiques qu'ils recommandent:

  1. Écrivez des programmes pour les gens, pas pour les ordinateurs
  2. Laissez l'ordinateur faire le travail
  3. Apportez des modifications incrémentielles
  4. Ne vous répétez pas (ou d'autres)
  5. Prévoyez les erreurs
  6. Optimiser le logiciel uniquement après son bon fonctionnement
  7. Conception et objectif du document, pas la mécanique
  8. Collaborer

Le papier va dans le détail considérable sur chacun de ces points.

17
David Moles

Est-il vraiment logique d'écrire du code, c'est-à-dire OOP quand des choses standard auraient pu être faites, auraient été beaucoup plus rapides à écrire et auraient été d'un niveau de lisibilité similaire en raison de la brièveté du programme?

Réponse personnelle:
Je fais aussi beaucoup de scripts à des fins scientifiques. Pour les petits scripts, j'essaie simplement de suivre les bonnes pratiques de programmation générales (c'est-à-dire utiliser le contrôle de version, pratiquer l'auto-contrôle avec des noms de variables). Si je suis en train d'écrire quelque chose pour ouvrir ou visualiser rapidement un ensemble de données, je ne me soucie pas de la POO.

Réponse générale:
"Ça dépend." Mais si vous avez du mal à comprendre quand utiliser un concept ou des paradigmes de programmation, voici quelques éléments à considérer:

  • Évolutivité: le script sera-t-il autonome ou sera-t-il éventuellement utilisé dans un programme plus vaste? Si oui, la programmation plus large utilise-t-elle la POO? Le code de votre script peut-il être facilement intégré dans un programme plus vaste?
  • Modularité: En général, votre code doit être modulaire. Cependant, OOP divise le code en morceaux d'une manière très spéciale. Ce type de modularité (c'est-à-dire la division de votre script en classes) a-t-il un sens pour ce que vous faites?

Je veux savoir comment "recommencer" et programmer proprement sur ces projets plus petits et plus rapides.

# 1: Familiarisez-vous avec ce qui existe:
Même si vous êtes "juste" un script (et que vous vous souciez vraiment de la composante scientifique), vous devriez prendre un certain temps pour en apprendre davantage sur les différents concepts et paradigmes de programmation. De cette façon, vous pouvez avoir une meilleure idée de ce que vous devez/ne voulez pas utiliser et quand. Cela peut sembler un peu intimidant. Et vous pouvez toujours avoir la question, "Où dois-je commencer/que dois-je commencer à regarder?" J'essaie d'expliquer un bon point de départ dans les deux points suivants.

# 2: Commencez à corriger ce que vous savez être faux:
Personnellement, je commencerais par ce que je sais être faux. Obtenez un certain contrôle de version et commencez à vous discipliner pour vous améliorer avec ces noms de variables (c'est une lutte sérieuse). Corriger ce que vous savez être faux peut sembler évident. Cependant, d'après mon expérience, j'ai constaté que réparer une chose m'amène à autre chose, et ainsi de suite. Avant de le savoir, j'ai dévoilé 10 choses différentes que je faisais mal et compris comment les corriger ou comment les implémenter de manière propre.

# 3: Obtenez un partenaire de programmation:
Si "recommencer" pour vous n'implique pas de suivre des cours formels, envisagez de faire équipe avec un développeur et de lui demander de revoir votre code. Même s'ils ne comprennent pas la partie scientifique de ce que vous faites, ils pourraient vous dire ce que vous auriez pu faire pour rendre votre code plus élégant.

# 4: Rechercher des consortiums:
Je ne sais pas dans quel domaine scientifique vous vous trouvez. Mais selon ce que vous faites dans le monde scientifique, essayez de rechercher des consortiums, des groupes de travail ou des participants à la conférence. Vérifiez ensuite s'il existe des normes sur lesquelles ils travaillent. Cela peut vous conduire à certaines normes de codage. Par exemple, je fais beaucoup de travaux géospatiaux. L'examen des documents de conférence et des groupes de travail m'a conduit à Open Geospatial Consortium . Ils travaillent notamment sur des normes de développement géospatial.

J'espère que ça aide!


16
JustBlossom

Je recommanderais de m'en tenir au principe Unix: Keep It Simple, Stupid! (BAISER)

Ou, autrement dit: Faites une chose à la fois et faites-la bien.

Qu'est-ce que ça veut dire? Eh bien, tout d'abord, cela signifie que vos fonctions doivent être courtes. Toute fonction dont l'objectif, l'utilisation et la mise en œuvre ne peuvent pas être entièrement compris en quelques secondes est définitivement trop longue. Il est probable qu'il fasse plusieurs choses à la fois, chacune devant être une fonction qui leur est propre. Alors divisez-le.

En termes de lignes de code, mon heuristique est que 10 lignes sont une bonne fonction, et tout ce qui dépasse 20 est très probablement de la merde. D'autres personnes ont d'autres heuristiques. L'important est de réduire la longueur à quelque chose que vous pouvez réellement saisir en un instant.

Comment divisez-vous une fonction longue? Eh bien, commencez par chercher des motifs de code répétitifs. Ensuite, vous factorisez ces modèles de code, donnez-leur un nom descriptif et regardez votre code rétrécir. Vraiment, le meilleur refactoring est le refactoring qui réduit la taille du code.

Cela est particulièrement vrai lorsque la fonction en question a été programmée avec du copier-coller. Chaque fois que vous voyez un motif aussi répété, vous savez instantanément que cela devrait probablement être transformé en une fonction qui lui est propre. C'est le principe de Ne vous répétez pas (SEC) . Chaque fois que vous appuyez sur copier-coller, vous faites quelque chose de mal! Créez plutôt une fonction.

Anecdote
J'ai passé plusieurs mois à refactoriser du code qui avait des fonctions d'environ 500 lignes chacune. Une fois terminé, le code total était environ mille lignes plus court; J'avais produit une sortie négative en termes de lignes de code. Je devais la société ( http://www.geekherocomic.com/2008/10/09/programmers-salary-policy/index.html ). Pourtant, je crois fermement que ce fut l'une de mes œuvres les plus précieuses que j'ai jamais faites ...

Certaines fonctions peuvent être longues car elles font plusieurs choses distinctes les unes après les autres. Ce ne sont pas des violations DRY, mais elles peuvent également être fractionnées. Le résultat est souvent une fonction de haut niveau qui appelle une poignée de fonctions qui implémentent les étapes individuelles des fonctions d'origine. Cela augmentera généralement la taille du code, mais les noms de fonction ajoutés font des merveilles pour rendre le code plus lisible. Parce que maintenant vous avez une fonction de niveau supérieur avec toutes ses étapes explicitement nommées. De plus, après cette division, il est clair quelle étape fonctionne sur quelles données. (Fonction Vous n'utilisez pas de variables globales, n'est-ce pas?)

Une bonne heuristique pour ce type de division de fonction sectionnelle est chaque fois que vous êtes tenté d'écrire un commentaire de section, ou lorsque vous trouvez un commentaire de section dans votre code. C'est très probablement l'un des points où votre fonction doit être divisée. Le commentaire de section peut également servir à inspirer un nom pour la nouvelle fonction.

Les principes KISS et DRY peuvent vous prendre beaucoup de temps. Vous n'avez pas besoin de commencer par OOP etc.) immédiatement, vous pouvez souvent réaliser de grandes simplifications en appliquant simplement ces deux. Cependant, il est payant à long terme de connaître OOP et d'autres paradigmes car ils vous donnent des outils supplémentaires que vous pouvez utiliser pour rendre votre code de programme plus clair.

Enfin, enregistrez chaque action avec un commit. Vous factorisez quelque chose dans une nouvelle fonction, c'est un commit. Vous fusionnez deux fonctions en une seule, car elles font vraiment la même chose, c'est un commit. Si vous renommez une variable, c'est un commit. Engagez-vous fréquemment. Si un jour passe et que vous ne vous engagez pas, vous avez probablement fait quelque chose de mal.

Je suis d'accord avec les autres pour dire que le contrôle de version résoudra immédiatement bon nombre de vos problèmes. Plus précisément:

  • Pas besoin de maintenir une liste des changements qui ont été apportés, ou d'avoir beaucoup de copies d'un fichier, etc. car c'est ce que le contrôle de version prend en charge.
  • Plus de fichiers perdus en raison d'écrasements, etc. (tant que vous vous en tenez aux principes de base; par exemple, évitez "l'historique de réécriture")
  • Pas besoin de laisser des commentaires obsolètes, du code mort, etc. "juste au cas où"; une fois qu'ils sont engagés dans le contrôle des versions, n'hésitez pas à les neutraliser. Cela peut sembler très libérateur!

Je dirais qu'il ne faut pas trop y penser: utilisez simplement git. Restez fidèle aux commandes simples (par exemple, une seule branche master), utilisez peut-être une interface graphique, et ça devrait aller. En prime, vous pouvez utiliser gitlab, github, etc. pour la publication et les sauvegardes gratuites;)

La raison pour laquelle j'ai écrit cette réponse était de répondre à deux choses que vous pourriez essayer que je n'ai pas vues mentionnées ci-dessus. La première consiste à utiliser les assertions comme une alternative légère aux tests unitaires. Les tests unitaires ont tendance à se situer "en dehors" de la fonction/du module/de tout ce qui est testé: ils envoient généralement des données dans une fonction, reçoivent un résultat en retour, puis vérifient certaines propriétés de ce résultat. C'est généralement une bonne idée, mais cela peut être gênant (en particulier pour le code "jetable") pour plusieurs raisons:

  • Les tests unitaires doivent décider quelles données ils donneront à la fonction. Ces données doivent être réalistes (sinon cela ne sert à rien de les tester), elles doivent avoir le bon format, etc.
  • Les tests unitaires doivent avoir "accès" aux choses qu'ils veulent affirmer. En particulier, les tests unitaires ne peuvent vérifier aucune des données intermédiaires à l'intérieur d'une fonction; il faudrait séparer cette fonction en morceaux plus petits, tester ces morceaux et les brancher ensemble ailleurs.
  • Les tests unitaires sont également supposés pertinents pour le programme. Par exemple, les suites de tests peuvent devenir "périmées" s'il y a eu des changements importants depuis leur dernière exécution, et il peut même y avoir un tas de tests pour du code qui n'est même plus utilisé.

Les assertions n'ont pas ces inconvénients, car elles sont vérifiées lors de l'exécution normale d'un programme. En particulier:

  • Puisqu'ils sont exécutés dans le cadre de l'exécution normale du programme, nous avons des données réelles avec lesquelles jouer. Cela ne nécessite pas de conservation séparée et (par définition) est réaliste et a le bon format.
  • Les assertions peuvent être écrites n'importe où dans le code, nous pouvons donc les placer là où nous avons accès aux données que nous voulons vérifier. Si nous voulons tester une valeur intermédiaire dans une fonction, nous pouvons simplement mettre quelques assertions au milieu de cette fonction!
  • Puisqu'elles sont écrites en ligne, les assertions ne peuvent pas être "désynchronisées" avec la structure du code. Si nous nous assurons que les assertions sont vérifiées par défaut, nous n'avons pas non plus à nous soucier qu'elles deviennent "périmées" car nous verrons immédiatement si elles passent ou non la prochaine fois que nous exécuterons le programme!

Vous mentionnez la vitesse comme facteur, auquel cas la vérification des assertions peut être indésirable dans cette boucle (mais toujours utile pour vérifier la configuration et le traitement ultérieur). Cependant, presque toutes les implémentations d'assertions permettent de les désactiver; par exemple en Python ils peuvent apparemment être désactivés en exécutant le -O option (je ne le savais pas, car je n'avais jamais ressenti le besoin de désactiver mes assertions auparavant). Je vous recommande de les laisser on par défaut; si votre cycle de codage/débogage/test ralentit, vous feriez mieux de tester avec un sous-ensemble plus petit de vos données, ou d'effectuer moins d'itérations d'une simulation pendant le test, ou autre. Si vous finissez par désactiver des assertions dans des exécutions non-test pour des raisons de performances, la première chose que je vous recommande est mesure si elles sont en fait la source du ralentissement! (Il est très facile de se faire des illusions en ce qui concerne les goulots d'étranglement des performances)

Mon dernier conseil serait d'utiliser un système de build qui gère vos dépendances. Personnellement, j'utilise Nix pour cela, mais j'ai également entendu de bonnes choses sur Guix . Il existe également des alternatives comme Docker, qui sont beaucoup moins utiles d'un point de vue scientifique mais peut-être un peu plus familières.

Des systèmes comme Nix ne sont que récemment devenus (un peu) populaires, et certains pourraient les considérer comme excessifs pour le code "jetable" comme vous le décrivez, mais leur avantage pour la reproductibilité du calcul scientifique est énorme. Considérez un script Shell pour exécuter une expérience, comme celui-ci (par exemple run.sh):

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

Nous pouvons le réécrire dans une "dérivation" Nix à la place, comme ceci (par exemple run.nix):

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

Le truc entre ''...'' est du code bash, le même que nous avions auparavant, sauf que ${...} peut être utilisé pour "épisser" le contenu d'autres chaînes (dans ce cas ./., qui se développera jusqu'au chemin du répertoire contenant run.nix). Le with import ... line importe Nix's bibliothèque standard , qui fournit runCommand pour exécuter le code bash. Nous pouvons exécuter notre expérience en utilisant nix-build run.nix, qui donnera un chemin comme /nix/store/1wv437qdjg6j171gjanj5fvg5kxc828p-output.csv.

Alors qu'est-ce que cela nous apporte? Nix mettra automatiquement en place un environnement "propre", qui n'a accès qu'aux choses que nous avons explicitement demandées. En particulier, il n'a pas accès à des variables comme $HOME ou l'un des logiciels système que nous avons installés. Cela rend le résultat indépendant des détails de notre machine actuelle, comme le contenu de ~/.config ou les versions des programmes que nous avons installés; AKA ce qui empêche les autres de reproduire nos résultats! C'est pourquoi j'ai ajouté cette commande cp, car le projet ne sera pas accessible par défaut. Cela peut sembler ennuyeux que le logiciel du système ne soit pas disponible pour un script Nix, mais il va dans l'autre sens aussi: nous n'avons besoin de rien installé sur notre système (autre que Nix) pour l'utiliser dans un script; nous le demandons juste et Nix va aller chercher/compiler/tout ce qui est nécessaire (la plupart des choses seront téléchargées sous forme de binaires; la bibliothèque standard est également énorme!). Par exemple, si nous voulons un tas de packages particuliers Python et Haskell, pour certaines versions particulières de ces langages, plus quelques autres indésirables (car pourquoi pas?):

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

Le même nix-build run.nix exécutera ceci, en récupérant tout ce que nous avons demandé en premier (et en le mettant en cache au cas où nous le voudrions plus tard). La sortie (tout fichier/répertoire appelé $out) sera stocké par Nix, qui est le chemin qu'il crache. Il est identifié par le hachage cryptographique de toutes les entrées que nous avons demandées (contenu du script, autres packages, noms, drapeaux du compilateur, etc.); ces autres paquets sont identifiés par des hachages d'entrées leur, et ainsi de suite de telle sorte que nous avons une chaîne complète de providence pour tout, directement à la version de GCC qui a compilé la version de GCC qui a compilé bash, et ainsi de suite!

J'espère que j'ai montré que cela nous achète beaucoup pour le code scientifique et qu'il est relativement facile de commencer. Cela commence également à être pris très au sérieux par les scientifiques, par exemple (top hit Google) https://dl.acm.org/citation.cfm?id=2830172 pourrait donc être une compétence précieuse à cultiver (tout comme la programmation)

11
Warbo

Sans passer par la mentalité de contrôle de version à part entière + emballage + tests unitaires (qui sont de bonnes pratiques de programmation que vous devriez essayer de réaliser à un moment donné), une solution intermédiaire que je pense conviendrait serait d'utiliser Jupiter Notebook . Cela semble mieux s'intégrer au calcul scientifique.

Il a l'avantage que vous pouvez mélanger vos pensées avec le code; expliquer pourquoi une approche est meilleure qu'une autre et laisser l'ancien code tel quel dans une section ad hoc. En plus d'utiliser correctement les cellules vous amènera naturellement à fragmenter votre code et à l'organiser en fonctions qui peuvent aider à sa compréhension.

9

Les meilleures réponses sont déjà bonnes, mais je voulais répondre directement à certaines de vos questions.

Des tests unitaires sont-ils nécessaires pour écrire de petits morceaux de code?

La taille du code n'est pas directement liée à la nécessité de tests unitaires. Il est lié indirectement: les tests unitaires ont plus de valeur dans les bases de code complexes et les petites bases de code ne sont généralement pas aussi complexes que les plus grandes.

Les tests unitaires brillent pour le code où il est facile de faire des erreurs, ou lorsque vous allez avoir de nombreuses implémentations de ce code. Les tests unitaires font peu pour vous aider avec le développement actuel , mais ils font beaucoup pour vous éviter de faire des erreurs à l'avenir qui causent du code existant pour se conduire soudainement mal (même si vous n'avez pas touché à cette chose).

Supposons que vous ayez une application dans laquelle la bibliothèque A effectue la quadrature des nombres et la bibliothèque B applique le théorème de Pythagore. Évidemment, B dépend de A. Vous devez corriger quelque chose dans la bibliothèque A, et disons que vous introduisez un bogue qui cube les nombres au lieu de les mettre au carré.

La bibliothèque B commencera soudainement à se comporter mal, peut-être à lancer des exceptions ou à donner simplement une mauvaise sortie. Et quand vous regardez l'historique des versions de la bibliothèque B, vous voyez qu'elle est intacte. Le résultat final problématique est que vous n'avez aucune indication de ce qui pourrait mal se passer, et vous devrez déboguer le comportement de B avant de vous rendre compte que le problème est en A. C'est un effort inutile.

Entrez les tests unitaires. Ces tests confirment que la bibliothèque A fonctionne comme prévu. Si vous introduisez un bogue dans la bibliothèque A qui provoque un mauvais résultat, vos tests unitaires le détecteront. Par conséquent, vous ne serez pas bloqué en essayant de déboguer la bibliothèque B.
Cela dépasse votre portée, mais dans un développement d'intégration continue, des tests unitaires sont exécutés chaque fois que quelqu'un valide du code, ce qui signifie que vous saurez que vous avez cassé quelque chose dès que possible.

En particulier pour les opérations mathématiques complexes, les tests unitaires peuvent être une bénédiction. Vous effectuez quelques exemples de calculs, puis vous écrivez des tests unitaires qui comparent votre sortie calculée et votre sortie réelle (sur la base des mêmes paramètres d'entrée).

Cependant, notez que les tests unitaires ne vous aideront pas à créer du bon code, mais plutôt à maintenir ça. Si vous écrivez habituellement du code une fois et ne le revoyez jamais, les tests unitaires seront moins bénéfiques.

Et la POO?

La POO est une façon de penser sur des entités distinctes, par exemple:

Lorsqu'un Customer veut acheter un Product, il parle au Vendor pour recevoir un Order. Le Accountant paiera alors le Vendor.

Comparez cela à la façon dont un programmeur fonctionnel pense aux choses:

Quand un client veut purchaseProduct(), il talktoVendor() pour qu'ils lui sendOrder(). Le comptable va alors payVendor().

Pommes et oranges. Aucun d'eux n'est objectivement meilleur que l'autre. Une chose intéressante à noter est que pour la POO, Vendor est mentionné deux fois mais il se réfère à la même chose. Cependant, pour la programmation fonctionnelle, talktoVendor() et payVendor() sont deux choses distinctes.
Cela montre la différence entre les approches. S'il y a beaucoup de logique spécifique au fournisseur partagée entre ces deux actions, alors OOP aide à réduire la duplication de code. Cependant, s'il n'y a pas de logique partagée entre les deux, puis les fusionner en une seule Vendor est un travail futile (et donc la programmation fonctionnelle est plus efficace).

Plus souvent qu'autrement, les calculs mathématiques et scientifiques sont des opérations distinctes qui ne reposent pas sur des logiques/formules partagées implicites. Pour cette raison, la programmation fonctionnelle est plus souvent utilisée que la POO.

Quelles sortes d'approches sont bonnes pour écrire rapidement du bon code propre lors de la "programmation scientifique" plutôt que de travailler sur des projets plus importants?

Votre question implique que la définition de "bon code propre" change, que vous fassiez de la programmation scientifique ou que vous travailliez sur des projets plus importants (je suppose que vous voulez dire entreprise).

La définition d'un bon code ne change pas. Le besoin d'éviter la complexité (ce qui peut être fait en écrivant du code propre), cependant, change.

Le même argument revient ici.

  • Si vous ne revisitez jamais l'ancien code et ne comprenez pas entièrement la logique sans avoir à le compartimenter, ne faites pas d'efforts excessifs pour rendre les choses maintenables.
  • Si vous revisitez l'ancien code, ou si la logique requise est trop complexe pour que vous puissiez vous attaquer à la fois (vous obligeant ainsi à compartimenter les solutions), concentrez-vous alors sur l'écriture d'une fermeture propre et réutilisable.

Je pose ces questions car souvent, la programmation elle-même n'est pas super complexe. C'est plus sur les mathématiques ou les sciences que je teste ou recherche avec la programmation.

J'obtiens la distinction que vous faites ici, mais quand vous regardez en arrière le code existant, vous regardez à la fois les mathématiques et la programmation. Si soit est artificiel ou complexe, alors vous aurez du mal à le lire.

Par exemple, une classe est-elle nécessaire lorsque deux variables et une fonction pourraient probablement s'en occuper?

Mis à part les principes de POO, la principale raison pour laquelle j'écris des classes pour héberger quelques valeurs de données est parce que simplifie la déclaration des paramètres de méthode et des valeurs de retour. Par exemple, si j'ai beaucoup de méthodes qui utilisent un emplacement (paire lat/lon), alors je me lasserai rapidement d'avoir à taper float latitude, float longitude et préférera de loin écrire Location loc.

Cela est encore aggravé lorsque vous considérez que les méthodes retournent généralement une valeur (à moins que des fonctionnalités spécifiques au langage existent pour renvoyer plus de valeurs), et des choses comme un emplacement voudraient que vous retourniez deux valeurs (lat + lon). Cela vous incite à créer une classe Location pour simplifier votre code.

Par exemple, une classe est-elle nécessaire lorsque deux variables et une fonction pourraient probablement s'en occuper?

Une autre chose intéressante à noter est que vous pouvez utiliser OOP sans mélanger les valeurs et les méthodes de données. Tous les développeurs ne sont pas d'accord ici (certains l'appellent un anti-modèle), mais vous pouvez avoir des modèles de données anémiques là où vous les avez séparés classes de données (stocke les champs de valeur) et classes logiques (stocke les méthodes).
C'est, bien sûr, sur un spectre. Vous n'avez pas besoin d'être parfaitement anémique, vous pouvez l'utiliser lorsque vous le jugez approprié.

Par exemple, une méthode qui concatène simplement le prénom et le nom d'une personne peut toujours être hébergée dans la classe Person elle-même, car ce n'est pas vraiment une "logique" mais plutôt une valeur calculée.

(Considérez que ce sont généralement des situations où la vitesse du programme est préférée pour être plus rapide - lorsque vous exécutez plus de 25 000 000 pas de temps d'une simulation, vous voulez un peu qu'elle soit.)

Une classe est toujours aussi grande que la somme de ses champs. En reprenant l'exemple de Location, qui se compose de deux valeurs float, il est important de noter ici qu'un seul objet Location occupera autant de mémoire que deux float valeurs.

Dans ce sens, peu importe que vous utilisiez OOP ou non. L'empreinte mémoire est la même.

La performance elle-même n'est pas non plus un grand obstacle à franchir. La différence entre par exemple l'utilisation d'une méthode globale ou d'une méthode de classe n'a rien à voir avec les performances d'exécution, mais a tout à voir avec la génération de bytecode au moment de la compilation.

Pensez-y de cette façon: si j'écris ma recette de gâteau en anglais ou en espagnol ne change pas le fait que le gâteau mettra 30 minutes à cuire (= performance d'exécution). La seule chose que la langue de la recette change est la façon dont le cuisinier mélange les ingrédients (= compilation du bytecode).

Pour Python spécifiquement, vous n'avez pas besoin de précompiler explicitement le code avant de l'appeler. Cependant, lorsque vous ne précompilez pas, le la compilation se produira en essayant d'exécuter le code. Quand je dis "runtime", je veux dire l'exécution elle-même, pas la compilation qui pourrait précéder l'exécution.

6
Flater

Les outils du métier sont généralement inventés pour répondre à un besoin. Si vous avez besoin d'utiliser l'outil, sinon, vous n'avez probablement pas à le faire.

Plus précisément, les programmes scientifiques ne sont pas la cible finale, ils sont les moyens. Vous écrivez le programme pour résoudre un problème que vous avez actuellement - vous ne vous attendez pas à ce que ce programme soit utilisé par d'autres (et qu'il doive être maintenu) dans dix ans. Cela signifie à lui seul que vous n'avez pas avoir à penser à l'un des outils qui permettent au développeur actuel d'enregistrer l'historique pour d'autres, comme le contrôle de version, ou de capturer des fonctionnalités dans du code comme des tests unitaires.

Qu'est-ce qui vous profiterait alors?

  • le contrôle de version est agréable car il vous permet de sauvegarder très facilement votre travail. Depuis 2018, github est un endroit très populaire pour le faire (et vous pouvez toujours le déplacer plus tard si nécessaire - git est très flexible). Un substitut bon marché et simple pour les sauvegardes sont les procédures de sauvegarde automatique dans votre système d'exploitation (Time Machine pour Mac, rsync pour Linux, etc.). Votre code doit être à plusieurs endroits!
  • Les tests unitaires sont agréables car si vous les écrivez premier vous êtes obligé de réfléchir à la façon de vérifier ce que le code fait réellement, ce qui vous aide à concevoir une API plus utile pour votre code. Ceci est utile si vous vous lancez dans l'écriture de code pour être réutilisé plus tard et vous aide lors de la modification d'un algorithme car vous savez que cela fonctionne dans ces cas.
  • Documentation. Apprenez à écrire la documentation appropriée dans le langage de programmation que vous utilisez (javadoc pour Java par exemple). Écrivez pour l'avenir vous. Dans ce processus, vous constaterez que de bons noms de variables facilitent la documentation. Itérer. Accordez autant de soin à votre documentation qu'un poète aux poèmes.
  • Utilisez de bons outils. Trouvez un IDE qui aide vous et apprenez-le bien. La refactorisation comme renommer des variables en un meilleur nom est beaucoup plus facile de cette façon.
  • Si vous avez des pairs, envisagez d'utiliser l'examen par les pairs. Avoir un étranger qui regarde et comprend votre code est la version ici et maintenant du futur pour lequel vous écrivez. Si votre pair ne comprend pas votre code, vous ne le comprendrez probablement pas plus tard.

Avantages d'un code scientifique propre

  • ... en regardant les livres de programmation, ils semblent souvent être abordés dans des projets plus importants.

  • ... est-il vraiment logique d'écrire du code, c'est-à-dire OOP quand des choses standard auraient pu être faites, auraient été beaucoup plus rapides à écrire et auraient été d'un niveau de lisibilité similaire à cause de la brièveté du programme?

Il pourrait être utile de considérer votre code du point de vue d'un futur codeur.

  • Pourquoi ont-ils ouvert ce fichier?
  • Que cherchent-ils?

Selon mon expérience,

Un code propre devrait faciliter la vérification de vos résultats

  • Aidez les utilisateurs à savoir exactement ce qu'ils doivent faire pour exécuter votre programme.
  • Vous souhaiterez peut-être diviser votre programme afin que les algorithmes individuels puissent être comparés séparément.

  • Évitez d'écrire des fonctions avec des effets secondaires contre-intuitifs où une opération non liée entraîne le comportement différent d'une autre opération. Si vous ne pouvez pas l'éviter, documentez ce dont votre code a besoin et comment le configurer.

Un code propre peut servir d'exemple de code pour les futurs codeurs

Des commentaires clairs (y compris ceux qui montrent comment les fonctions doivent être appelées) et des fonctions bien séparées peuvent faire une énorme différence dans le temps qu'il faut à quelqu'un qui commence (ou à vous) pour faire quelque chose d'utile à partir de votre travail.

En plus de cela, faire une véritable "API" pour votre algorithme peut vous préparer mieux si vous décidez de transformer vos scripts en une véritable bibliothèque pour quelqu'un d'autre.

Recommandations

"Citez" des formules mathématiques à l'aide de commentaires.

  • Ajoutez des commentaires pour "citer" des formules mathématiques, surtout si vous avez utilisé des optimisations (identités trigonométriques, séries Taylor, etc.).
  • Si vous avez obtenu la formule du livre, ajoutez un commentaire disant John Smith Method from Some Book 1st Ed. Section 1.2.3 Pg 180, si vous avez trouvé la formule sur un site Web ou dans un document, citez-la également.
  • Je recommanderais d'éviter les commentaires "lien uniquement", assurez-vous de vous référer à la méthode par son nom quelque part pour permettre aux gens de la rechercher sur Google, j'ai rencontré certains commentaires "lien uniquement" qui ont redirigé vers d'anciennes pages internes et ils peuvent être très frustrant .
  • Vous pouvez essayer de taper la formule dans votre commentaire s'il est toujours facile à lire en Unicode/ASCII, mais cela peut devenir très gênant (les commentaires de code ne sont pas LaTeX).

Utilisez judicieusement les commentaires

Si vous pouvez améliorer la lisibilité de votre code en utilisant de bons noms de variables/noms de fonction, faites-le d'abord. N'oubliez pas que les commentaires resteront éternellement jusqu'à ce que vous les supprimiez, alors essayez de faire des commentaires qui ne seront pas obsolètes.

Utiliser des noms de variables descriptives

  • Les variables d'une seule lettre peuvent être la meilleure option si elles font partie d'une formule.
  • Il peut être crucial pour les futurs lecteurs de pouvoir regarder le code que vous avez écrit et le comparer à l'équation que vous implémentez.
  • Le cas échéant, envisagez d'y ajouter un suffixe pour décrire sa véritable signification, par exemple, xBar_AverageVelocity
  • Comme mentionné précédemment, je recommande d'indiquer clairement la formule/méthode que vous utilisez par nom dans un commentaire quelque part.

Écrivez du code pour exécuter votre programme contre les bonnes données connues et les mauvaises données connues.

Des tests unitaires sont-ils nécessaires pour écrire de petits morceaux de code?

Je pense que les tests unitaires peuvent être utiles, je pense que la meilleure forme de tests unitaires pour le code scientifique est une série de tests qui s'exécutent sur de mauvaises et bonnes données connues.

Écrivez du code pour exécuter votre algorithme et vérifiez à quel point le résultat s'écarte de ce que vous attendez. Cela vous aidera à trouver (potentiellement très mauvais et difficiles à trouver) des problèmes où vous codez accidentellement en dur quelque chose provoquant un résultat faussement positif, ou faites une erreur qui fait que la fonction renvoie toujours la même valeur.

Notez que cela peut être fait à n'importe quel niveau d'abstraction. Par exemple, vous pouvez tester l'intégralité d'un algorithme de correspondance de motifs, ou vous pouvez tester une fonction qui calcule simplement la distance entre deux résultats dans votre processus d'optimisation. Commencez par les domaines les plus cruciaux pour vos résultats en premier et/ou les parties du code qui vous préoccupent le plus.

Facilitez l'ajout de nouveaux cas de test, envisagez d'ajouter des fonctions "d'assistance" et structurez efficacement vos données d'entrée. Cela peut signifier éventuellement enregistrer les données d'entrée dans un fichier afin que vous puissiez facilement réexécuter les tests, mais soyez très prudent pour éviter les faux positifs ou les cas de test biaisés/résolus de manière triviale.

Pensez à utiliser quelque chose comme validation croisée , voir ce post sur la validation croisée pour plus d'informations.

Utiliser le contrôle de version

Je recommanderais d'utiliser le contrôle de version et d'héberger votre référentiel sur un site externe. Il existe des sites qui hébergeront des référentiels gratuitement.

Avantages:

  1. Il fournit une sauvegarde en cas de défaillance de votre disque dur
  2. Il fournit un historique qui vous évite de vous inquiéter si un problème récent est survenu en raison de la modification accidentelle d'un fichier, entre autres avantages.
  3. Il vous permet d'utiliser la ramification, ce qui est un bon moyen de travailler sur du code expérimental à long terme sans affecter le travail non lié.

Soyez prudent lorsque vous copiez/collez du code

Le style de mon code est compliqué et est rempli de commentaires obsolètes notant d'autres façons de faire quelque chose ou de lignes de code copiées.

  • Copier/coller du code peut vous faire gagner du temps, mais c'est l'une des choses les plus dangereuses que vous puissiez faire, surtout si c'est du code que vous n'avez pas écrit vous-même (par exemple, s'il s'agit du code d'un collègue).

  • Dès que vous obtenez le code fonctionnel et testé, je vous recommande de le parcourir très attentivement pour renommer toutes les variables ou commenter tout ce que vous ne comprenez pas.

6
jrh

Aussi bien que le contrôle de version et les tests unitaires permettent de garder votre code global organisé et fonctionnel, aucun ne vous aide à écrire un code plus propre.

  • Le contrôle de version vous permettra de voir comment et quand le code est devenu aussi salissant qu'il est.
  • Les tests unitaires garantiront que, même si le code est un gâchis complet, il fonctionne toujours.

Si vous voulez vous empêcher d'écrire du code désordonné, vous avez besoin d'un outil qui fonctionne là où les problèmes se produisent: lorsque vous écrivez le code. Un type d'outil populaire qui le fait est appelé un linter. Je ne suis pas un développeur python, mais il semble que Pylint pourrait être une bonne option.

Un linter examine le code que vous avez écrit et le compare à un ensemble configurable de meilleures pratiques. Si le linter a une règle selon laquelle les variables doivent être camelCase et que vous en écrivez une dans snake_case, cela signalera cela comme une erreur. Les bons linters ont des règles allant de "les variables déclarées doivent être utilisées" à "la complexité cyclomatique des fonctions doit être inférieure à 3".

La plupart des éditeurs de code peuvent être configurés pour exécuter un linter chaque fois que vous enregistrez, ou tout simplement en cours de frappe, et indiquent les problèmes en ligne. Si vous tapez quelque chose comme x = 7, le x sera mis en évidence, avec une instruction pour utiliser un nom plus long et meilleur (si c'est ce que vous avez configuré). Cela fonctionne comme la vérification orthographique dans la plupart des traitements de texte, ce qui le rend difficile à ignorer et aide à créer de meilleures habitudes.

5
Mike Gossmann

En plus des bons conseils déjà ici, vous voudrez peut-être considérer le but de votre programmation, et donc ce qui est important pour vous.

"Il s'agit davantage de mathématiques ou de sciences que je teste ou recherche avec la programmation."

Si le but est d'expérimenter et de tester quelque chose pour votre propre compréhension et que vous savez quels devraient être les résultats, votre code est essentiellement à jeter rapidement et votre approche actuelle peut suffire, bien qu'elle puisse être améliorée. Si les résultats ne sont pas ceux attendus, vous pouvez revenir en arrière et revoir.

Cependant, si les résultats de votre codage informent la direction de votre recherche et que vous ne savez pas quels sont les résultats devrait, l'exactitude devient particulièrement importante. Une erreur dans votre code pourrait vous conduire à tirer les mauvaises conclusions de votre expérience avec une variété de mauvaises implications pour votre recherche globale.

Dans ce cas, décomposer votre code en fonctions facilement compréhensibles et vérifiables avec des tests unitaires vous donnera des briques de construction plus solides vous donnant plus de confiance dans vos résultats et vous épargnera beaucoup de frustration plus tard.

5
Aidan

En plus de toutes les bonnes suggestions fournies jusqu'à présent, une pratique que j'ai apprise au fil du temps et que je trouve essentielle consiste à ajouter très généreusement des commentaires détaillés à votre code. C'est la chose la plus importante pour moi quand je reviens à quelque chose après un long laps de temps. Expliquez-vous ce que vous pensez. Cela prend un peu de temps mais c'est relativement facile et surtout indolore.

J'ai parfois deux ou trois fois plus de lignes de commentaires que de code, surtout lorsque les concepts ou les techniques sont nouveaux pour moi et doivent m'expliquer.

Faites le contrôle de version, améliorez vos pratiques, etc .... tout cela. Mais expliquez-vous les choses au fur et à mesure. Ça marche vraiment bien.

4
Bob Newell

Tout ce que vous avez répertorié est un outil de la boîte à outils métaphorique. Comme tout dans la vie, différents outils conviennent à différentes tâches.

Comparé à d'autres domaines d'ingénierie, le logiciel fonctionne avec un tas de pièces individuelles qui, en elles-mêmes, sont assez simples. Une déclaration d'affectation n'évalue pas différemment en fonction des fluctuations de température de la pièce. Une instruction if ne se corrode pas et continue de renvoyer la même chose après un certain temps. Mais parce que les éléments individuels sont si simples et les logiciels créés par des humains, ces éléments sont combinés en morceaux de plus en plus grands jusqu'à ce que le résultat devienne si grand et complexe qu'il atteigne les limites de ce que les gens peuvent gérer mentalement.

Au fur et à mesure que les projets logiciels grandissaient, les gens les ont étudiés et ont créé des outils pour essayer de gérer cette complexité. OOP est un exemple. De plus en plus de langages de programmation abstraits sont un autre moyen. Parce qu'une grande partie de l'argent dans le logiciel fait plus plus plus , des outils pour y parvenir sont ce que vous allez voir et lire, mais il semble que ces situations ne s'appliquent pas à vous.

Donc, ne vous sentez pas comme vous besoin pour faire tout cela. Au bout du compte, le code n'est qu'un moyen de parvenir à une fin. Malheureusement, ce qui vous donnera le meilleur point de vue sur ce qui est et ce qui n'est pas approprié, c'est de travailler sur des projets plus importants, car il est beaucoup plus difficile de savoir ce qui manque lorsque la boîte à outils est votre esprit.

Dans tous les cas, je ne m'inquiéterais pas de ne pas utiliser OOP ou d'autres techniques tant que vos scripts sont petits. La plupart des problèmes que vous avez décrits ne sont que des compétences organisationnelles professionnelles générales, c'est-à-dire ne pas perdre un l'ancien fichier est quelque chose que tous les champs doivent gérer.

4
whatsisname

J'ai travaillé dans un environnement similaire avec des universitaires qui écrivent beaucoup de code (math/science) mais leur progression est lente pour les mêmes raisons que celles que vous décrivez. Cependant, j'ai remarqué une chose particulière qui s'est bien passée et qui, je pense, peut également vous aider: constituer et maintenir une collection de bibliothèques spécialisées pouvant être utilisées dans plusieurs projets. Ces bibliothèques devraient fournir des fonctions utilitaires et aideront donc à garder votre projet actuel spécifique au domaine problématique.

Par exemple, vous devrez peut-être faire face à de nombreuses transformations de coordonnées dans votre domaine (ECEF, NED, lat/lon, WGS84 etc.), donc une fonction comme convert_ecef_to_ned() devrait aller dans un nouveau projet appelé CoordinateTransformations. Mettez le projet sous contrôle de version et hébergez-le sur les serveurs de votre service afin que d'autres personnes puissent l'utiliser (et, espérons-le, l'améliorer). Ensuite, après quelques années, vous devriez avoir une solide collection de bibliothèques avec vos projets contenant uniquement du code spécifique à un domaine de recherche/problème particulier.

Quelques conseils plus généraux:

  • Visez toujours à modéliser votre problème aussi précisément que possible, quel qu'il soit. De cette façon, les questions de conception de logiciels telles que quoi/où/comment mettre une variable devraient devenir beaucoup plus évidentes à répondre.
  • Je ne m'embêterais pas avec le développement piloté par les tests, car le code scientifique décrit les idées et les concepts et est plus créatif et fluide; il n'y a pas d'API à définir, de services à maintenir, de risques pour le code des autres lors du changement de fonctionnalité, etc.
4
jigglypuff

Quelles qualités sont importantes pour ce type de programme?

Peu importe qu'il soit facile à entretenir ou à faire évoluer, car il est probable que cela ne se produira pas.

Peu importe son efficacité.

Peu importe qu'il dispose d'une excellente interface utilisateur ou qu'il soit sécurisé contre les attaquants malveillants.

Il peut être important qu'il soit lisible: que quelqu'un lisant votre code puisse facilement se convaincre qu'il fait ce qu'il prétend faire.

Il importe certainement que ce soit correct. Si le programme donne des résultats incorrects, ce sont vos conclusions scientifiques par la fenêtre. Mais il a seulement besoin de traiter correctement l'entrée que vous lui demandez de traiter; il n'a vraiment pas beaucoup d'importance s'il tombe si des valeurs de données d'entrée négatives sont données, si toutes vos valeurs de données sont positives.

Il est également important que vous conserviez un certain niveau de contrôle des modifications. Vos résultats scientifiques doivent être reproductibles, ce qui signifie que vous devez savoir quelle version du programme a produit les résultats que vous avez l'intention de publier. Comme il n'y a qu'un seul développeur, le contrôle des modifications n'a pas besoin d'être très élaboré, mais vous devez vous assurer que vous pouvez revenir en arrière et reproduire vos résultats.

Alors ne vous inquiétez pas des paradigmes de programmation, de l'orientation des objets, de l'élégance algorithmique. Ne vous inquiétez pas de la clarté et de la lisibilité et de la traçabilité de vos modifications dans le temps. Ne vous inquiétez pas de l'interface utilisateur. Ne vous inquiétez pas de tester toutes les combinaisons possibles de paramètres d'entrée, mais faites suffisamment de tests pour être sûr (et pour rassurer les autres) que vos résultats et conclusions sont valides.

4
Michael Kay

Ce qui suit sont mes opinions et très influencé par mon propre chemin particulier.

Le codage engendre souvent des perspectives dogmatiques sur la façon dont vous devez faire les choses. Au lieu de techniques et d'outils, je pense que vous devez examiner les valeurs et les coûts cumulatifs pour décider d'une stratégie appropriée.

Écrire un code solide, lisible, déboguable et solide prend beaucoup de temps et d'efforts. Dans de nombreux cas, étant donné un horizon de planification limité, cela ne vaut pas la peine (analyse paralysie).

Un collègue avait une règle d'or; si vous faites essentiellement le même genre de choses pour la troisième fois, investissez des efforts, sinon un travail rapide et sale est approprié.

Il est essentiel de tester quelque sorte, mais pour des projets ponctuels, un simple regard peut suffire. Pour tout ce qui est substantiel, les tests et l'infrastructure de test sont essentiels. La valeur est qu'elle vous libère lors du codage, le coût est que si le test se concentre sur une implémentation particulière, les tests doivent également être maintenus. Les tests vous rappellent également comment les choses sont supposées fonctionner.

Pour mes propres scripts uniques (souvent pour des choses comme la validation d'une estimation d'une probabilité, ou similaire), j'ai trouvé deux petites choses très utiles: 1. Inclure un commentaire montrant comment le code est utilisé. 2. Incluez une brève description de la raison pour laquelle vous avez écrit le code. Ces choses sont terriblement évidentes lorsque vous écrivez le code, mais l'évidence perd du temps :-).

La POO concerne la réutilisation du code, l'abrégé, l'encapsulation, la factorisation, etc. Très utile, mais facile à perdre si la production de code et de conception de qualité n'est pas votre objectif final. Il faut du temps et des efforts pour produire des trucs de qualité.

3
copper.hat

Bien que je pense que les tests unitaires ont leurs mérites, ils sont d'une valeur douteuse pour le développement scientifique - ils sont souvent juste trop petits pour offrir beaucoup de valeur.

Mais j'aime vraiment les tests d'intégration pour le code scientifique:

Isolez un petit morceau de votre code qui pourrait fonctionner seul, par exemple le pipeline ETL. Ensuite, écrivez un test qui fournit les données, exécutez le pipeline etl (ou juste une étape), puis testez que le résultat correspond à vos attentes. Bien que le morceau testé puisse contenir beaucoup de code, le test fournit toujours de la valeur:

  1. Vous avez un crochet pratique pour réexécuter votre code, ce qui aide à l'exécuter souvent.
  2. Vous pouvez tester certaines hypothèses dans votre test
  3. Si quelque chose se casse, il est facile d'ajouter un test qui échoue et de corriger le problème
  4. Vous codifiez les entrées/sorties attendues, en évitant les maux de tête habituels résultant de la tentative de deviner le format des données d'entrée.
  5. Bien qu'ils ne soient pas aussi simples que les tests unitaires, les tests informatiques aident toujours à séparer votre code et à vous forcer à ajouter des limites dans votre code.

J'utilise souvent cette technique et je me retrouve souvent avec une fonction principale lisible de manière relativiste, mais les sous-fonctions sont souvent assez longues et laides, mais peuvent être modifiées et réorganisées rapidement en raison de limites d'E/S robustes.

3
Christian Sauer

Je travaille normalement sur une très large base de sources. Nous utilisons tous les outils que vous mentionnez. Récemment, j'ai commencé à travailler sur certains scripts python pour un projet parallèle. Ils sont de quelques dizaines à quelques centaines de lignes tout au plus. Par habitude, j'ai engagé mes scripts pour le contrôle de code source. Cela a été utile car je peux créer des branches pour essayer des expériences qui pourraient ne pas fonctionner. Je peux bifurquer si je dois dupliquer le code et le modifier à une autre fin. Cela laisse l'original intact au cas où je devrais le ressortir .

Pour les "tests unitaires", je n'ai que quelques fichiers d'entrée qui sont destinés à produire une sortie connue que je vérifie à la main. Je pourrais probablement l'automatiser, mais il me semble que cela prendrait plus de temps que de gagner en le faisant. Cela dépend probablement de la fréquence à laquelle je dois modifier et exécuter les scripts. Quoi qu'il en soit, si cela fonctionne, faites-le. Si c'est plus difficile que ça ne vaut, ne perdez pas votre temps.

2
user1118321

Avec l'écriture de code - comme avec l'écriture en général - la question principale est:

Quel lecteur avez-vous en tête? ou Qui consomme votre code?

Des choses comme des directives de codage formelles n'ont aucun sens lorsque vous êtes votre seul public.

Cela étant dit, d'un autre côté, il serait utile d'écrire le code d'une manière, votre avenir vous permet de le comprendre tout de suite.

Donc, un "bon style" en serait un qui vous aide le plus. À quoi ce style devrait ressembler, c'est une réponse que je ne peux pas donner.

Je pense que vous n'avez pas besoin de OOP ou tests unitaires pour les fichiers de 150 LOC. Un VCS dédié serait intéressant lorsque vous avez du code en évolution. Sinon, un .bak fait l'affaire. Ces outils sont un remède à une maladie, vous n'en avez peut-être même pas.

Peut-être devriez-vous écrire votre code de telle manière que même si vous le lisez en étant ivre, vous êtes capable de le lire, le comprendre et le modifier.

2
Thomas Junk