科学的コードを書くときのきれいなプログラミング


169

私は大規模なプロジェクトを実際に書いていません。私は巨大なデータベースを維持したり、何百万行ものコードを扱ったりしていません。

私のコードは、主に「スクリプト」タイプのもの-数学関数をテストするためのもの、または何かをシミュレートするためのもの-「科学プログラミング」です。ここまでに取り組んだ最長のプログラムは数百行のコードであり、私が取り組んでいるプログラムのほとんどは約150行です。

私のコードもがらくたです。先日私が書いたファイルを見つけようとしていたので、これに気づきましたが、おそらく私は上書きしており、バージョン管理を使用していないことを知っています。これはおそらく私の愚かさに多くの人が苦しんでいます。

私のコードのスタイルは複雑で、何かを行う別の方法を記した古いコメントや、コピーされたコード行で満たされています。変数名は常に非常にわかりやすく説明的に始まりますが、たとえば、誰かがテストしたい新しいものを追加または変更すると、コードが上にオーバーレイされて上書きされます。このことをすぐにテストする必要があると思うので、私は安っぽい変数名を使用し始め、ファイルがポットに行くフレームワークを持っています。

私が現在取り組んでいるプロジェクトでは、私はこれらすべてが私に大きな意味で噛み付いてくる段階にあります。しかし、問題は(バージョン管理を使用し、新しい反復ごとに新しいファイルを作成し、そのすべてをどこかにテキストファイルに記録することを除いて、状況を劇的に改善する可能性があります)私の実際のコーディングスタイル。

より小さなコードを書くために単体テストは必要ですか?OOPはどうですか?大規模なプロジェクトで作業するのではなく、「科学的プログラミング」を行うときに、適切でクリーンなコードを迅速に記述するのにどのようなアプローチが適していますか?

多くの場合、プログラミング自体はそれほど複雑ではないため、これらの質問をします。私がプログラミングでテストまたは研究しているのは、数学または科学に関するものです。たとえば、2つの変数と関数がおそらくそれを処理できる場合、クラスは必要ですか?(これらは一般に、プログラムの速度が速いほうが望ましい状況であると考えてください-シミュレーションの25,000,000以上のタイムステップを実行しているとき、それを望んでいます。)

おそらくこれは広すぎるので、もしそうなら、私は謝罪しますが、プログラミングの本を見ると、しばしばより大きなプロジェクトで対処されているようです。私のコードはOOPを必要とせず、すでにかなり短いので、「ああ、でもそうすればファイルは1000行減ります!」これらの小さくて高速なプロジェクトで「やり直す」方法ときれいにプログラムする方法を知りたいです。

私はもっ​​と具体的な詳細を提供したいのですが、アドバイスがより一般的であればあるほど、役に立つと思います。Python 3でプログラミングしています。


誰かが重複を提案しました。標準のプログラミング標準を完全に無視することについては話していないことを明確にしましょう。明らかに、これらの標準が存在する理由があります。しかし、一方で、いくつかの標準的なことを実行できたときにOOPと呼ばれるコードを書くことは本当に意味がありますか?プログラム?

例外があります。さらに、科学的なプログラミングには、単なる標準を超えた標準がおそらくあります。私もそれらについて尋ねています。これは、科学的なコードを書くときに通常のコーディング標準を無視すべきかどうかではなく、きれいな科学的なコードを書くことです!


更新

「まったく1週間後ではない」種類の更新プログラムを追加すると思いました。あなたのアドバイスはすべてとても役に立ちました。現在、バージョン管理を使用しています-git、グラフィカルインターフェイス用のgit kraken。非常に使いやすく、ファイルを大幅にクリーンアップしました。古いファイルを残したり、「念のため」に古いバージョンのコードをコメントアウトしたりする必要はもうありません。

また、pylintをインストールし、すべてのコードで実行しました。最初に1つのファイルが負のスコアを獲得しました。それがどのように可能だったのかさえ私には分かりません。私のメインファイルは、〜1.83 / 10のスコアから始まり、〜9.1 / 10になりました。すべてのコードは現在、標準にかなり準拠しています。また、自分の目で行った変数名を更新しました。

特に、私はこのサイトで最近の質問をして、私の主な機能の1つをリファクタリングしました。そして今ではずっときれいで短くなっています。サイズと何が起こっているかを把握するのがはるかに簡単です。

次のステップは、ある種の「単体テスト」を実装することです。つまり、メインファイルで実行できるファイルで、アサートステートメントとtry / exceptsを使用して、その中のすべての関数を調べます。これはおそらく最善の方法ではなく、多くの重複したコードになります。しかし、私は読み続けて、それをより良くする方法を見つけようとします。

また、すでに作成したドキュメントを大幅に更新し、Excelスプレッドシート、ドキュメント、関連するペーパーなどの補足ファイルをgithubリポジトリに追加しました。それは今や本当のプログラミングプロジェクトのように見えます。

だから...私はこれがすべてだと思います:ありがとう




8
コードを積極的に改善したい場合は、Code Reviewに投稿することを検討してください。そこのコミュニティは喜んであなたを助けます。
hoffmale

7
「バージョン管理を使用し、新しい反復ごとに新しいファイルを作成し、そのすべてをテキストファイルにどこかに記録する」と言うとき、「と」は「または」を意味します。バージョン管理を使用している場合は、貼り付けバージョンをコピーしないでください。ポイントは、バージョン管理が古いバージョンをすべて保持することです
リチャードティングル

2
@mathreadlerあなたはまったく理解していないと思います。ええ、たぶん私は実際にコードを実際に読んで台無しにしようとしています(あなたは決して知らない、そして私は別の言語でプログラミングできる誰かと一緒に働いています)...しかし、コードはまだですくだらない。後でそれを読んで、私がやっていることをもう一度理解する必要があります。これ問題であり、私は今その効果を経験しているので、それを証言することができます。また、バージョン管理やここで提案した他の手法を実装することで事態はより簡単になりました。
ヘザー

回答:


163

これは科学者にとって非常に一般的な問題です。私はそれを見てきましたが、それは常にプログラミングが仕事をするためのツールとしてあなたが側で選ぶものであるという事実によって生じます。

あなたのスクリプトは混乱です。私は常識に反して、あなたが一人でプログラミングしていると仮定して、これはそれほど悪くないだろうと言いますあなたが二度と書くことのほとんどに触れることは決してないので、「価値」(スクリプトの結果)を生成する代わりにきれいなコードを書くのに時間を費やすことはあなたにとってあまり役に立たないでしょう。

ただし、やったことに戻って、何かがどのように機能していたのかを正確に確認する必要がある場合があります。さらに、他の科学者があなたのコードをレビューする必要がある場合、誰もがそれを理解できるように、できるだけ明確で簡潔であることが本当に重要です。

あなたの主な問題は読みやすさであるので、改善のためのいくつかのヒントがあります:

変数名:

科学者は簡潔な表記法を使用するのが大好きです。すべての数学方程式は通常、変数として1文字を使用します。コード内に非常に短い変数がたくさんあるのを見て驚かないでしょう。これは読みやすさを大きく傷つけます。コードに戻ると、それらのy、i、x2が何を表しているのか覚えていないので、それを理解しようとして多くの時間を費やすことになります。代わりに、変数を正確に表す名前を使用して、変数に明示的に名前を付けてみてください。

コードを関数に分割します。

すべての変数の名前を変更したため、方程式はひどく見え、複数行になります。

メインプログラムに残すのではなく、その方程式を別の関数に移動し、それに応じて名前を付けます。巨大で混乱したコード行を作成する代わりに、何が起こっているのか、どの方程式を使用したのかを正確に伝える短い指示が表示されます。これにより、実際の方程式を見て何をしたかを知る必要がないため、メインプログラムと方程式コード自体の両方が改善されます。別の関数では、必要に応じて変数に名前を付けて、戻ることができますより身近な一文字。

この考え方では、何かを表すコードのすべての部分を見つけてみてください。特にその何かがコード内で複数回行う必要がある場合は、それらを関数に分割してください。コードがすぐに読みやすくなり、コードを追加しなくても同じ関数を使用できることがわかります。

簡単に言えば、これらの関数がより多くのプログラムで必要な場合は、それらのライブラリを作成するだけで、いつでも利用できます。

グローバル変数:

私が初心者の頃、これはプログラムの多くのポイントで必要なデータを渡すのに最適な方法だと思いました。ものを渡す方法は他にもたくさんありますが、グローバル変数が行う唯一のことは、人々の頭痛を与えることです。プログラムのランダムなポイントに行くと、その値が最後に使用または編集されたタイミングがわからないためです追跡するのは苦痛です。可能な限り避けてください。

関数が複数の値を返すまたは変更する必要がある場合は、それらの値を持つクラスを作成してパラメーターとして渡すか、関数が複数の値(名前付きタプルを含む)を返し、呼び出し元コードでそれらの値を割り当てます。

バージョン管理

これは読みやすさを直接向上させるものではありませんが、上記のすべてを行うのに役立ちます。何らかの変更を行うたびに、バージョン管理にコミットし(ローカルGitリポジトリで十分です)、何かが機能しない場合は、変更内容を確認するか、単にロールバックしてください!これによりコードのリファクタリングがより簡単になり、誤って何かを壊した場合の安全策になります。

これらすべてを念頭に置いておくと、明確で効果的なコードを書くことができ、巨大な関数や乱雑な変数を歩く必要がないため、間違いの可能性をより迅速に見つけることができます。


56
素晴らしいアドバイス。しかし、「それほど悪くない」というコメントには賛成できません。とても悪いです。低品質の科学スクリプトは、データ分析の再現性にとって大きな問題であり、分析のエラーの頻繁な原因です。良いコードを書くことは、後でそれを理解できるようにするだけでなく、そもそもエラーを回避できるようにするためにも価値があります。
ジャックエイドリー

22
コードが既知の式の実装である場合、1文字の変数名などが適切な場合があります。読者に依存します...そして要点は、読者が名前の意味をすでに知っていることが期待されているかどうかです。
cHao

11
@cHao問題は、それらの変数が式にプラグインされているとき(「関数内で名前を変更する」アドバイス)ではなく、それらが変数の外で読み取られて操作され、他の変数と競合するときです(たとえば、3つの「x」変数にx1、x2、x3という名前が必要な人を見てきました)
-BgrWorker

4
「私は常識に反するつもりです...」いいえ、そうではありません。あなたは、常識に反する一般的な教義に反対しています。;)これは完全に適切なアドバイスです。
jpmc26

3
(元)科学プログラマーとして、私は通常3つのルールを採用しています。同様のコードを3回書くと、機能が具体化され、ドキュメント付きの別のモジュールに書き込まれます(多くの場合、コメントだけで十分ですが)。これにより、アドホックプログラミングの混乱が制限され、将来拡張できるライブラリを構築できます。
-rcollyer

141

ここの物理学者。行ったことがある。

あなたの問題は、ツールやプログラミングパラダイム(ユニットテスト、OOPなど)の選択に関するものではないと主張します。それは 態度、考え方です。変数名が最初は適切に選択されていて、最終的にがらくたになるという事実は十分に明らかになっています。コードを「一度実行してから捨てる」と考えると、必然的に混乱になります。あなたがそれを工芸と愛の産物と考えるなら、それは美しいでしょう。

きれいなコードを書くためのレシピは1つしかないと思います。それを実行しようとしているインタープリターのためではなく、それを読むつもりの人間のために書いてください。インタープリターは、コードが混乱しているかどうかは気にしませんが、人間の読者気にします。

あなたは科学者です。科学論文を磨くのに多くの時間を費やすことができるでしょう。最初のドラフトが複雑に見える場合、ロジックが最も自然な方法で流れるまでリファクタリングします。同僚にそれを読んでもらい、議論が非常に明確になるようにしたい。あなたはあなたの学生がそれから学ぶことができるようにしたいです。

きれいなコードを書くことはまったく同じです。コードは、たまたま偶然に機械可読なアルゴリズムの詳細な説明と考えてください。あなたがそれを人々が読む記事として出版することを想像してください。あなたも、会議でそれを見せて、行ごとにそれを通して聴衆を歩いて行くつもりです。次に、プレゼンテーションのリハーサルを行います。はい、ごとに!恥ずかしいですね。したがって、スライドをクリーンアップし(エラー...つまりコード)、もう一度リハーサルします。結果に満足するまで繰り返します。

リハーサルの後、想像上の人々やあなた自身の未来よりも、実際の人々に自分のコードを見せることができればなお良いでしょう。1行ずつそれを通過することは「コードウォーク」と呼ばれ、ばかげた練習ではありません。

もちろん、これらはすべてコストがかかります。クリーンなコードを書くことは取るたくさん 使い捨てのコードを書くよりも多くの時間を。特定のユースケースのメリットがメリットを上回るかどうかを評価できるのはあなただけです。

ツールとして、私は、彼らはありません前に言ったことが重要。ただし、1つを選択する必要がある場合は、バージョン管理が最も役立つと言えます。


32
«きれいなコードを書くことは、[明確な記事を書くこと]とまったく同じです。»私はそれを完全に支持します。
ジュアンダンサン

43
これは、ほとんどのプロのプログラマーが言うのを忘れていることです。コードは、他のプログラマーが読み取りおよび変更できるようになると完成します。実行して正しい出力を生成するときではありません。OPは、スクリプトをリファクタリングおよびコメント化して、人間が読めるようにするためにスクリプトごとに1時間余分に費やす必要があります。
UEFI

31
クリーンなコードを書くことが使い捨てコードを書くよりも多くの時間がかかるんが、はるかに重要なことです読ん使い捨てのコードはクリーンなコードを読むよりも多くの時間がかかります。
user949300

3
@UEFIいいえ、それはほとんどのプロのプログラマーが気付かないものです。または気にしないでください。
jpmc26

2
100%に同意します。統計学者はプログラマーになったので、私は職場でかなりの量の「科学的な」プログラミングをしています。わかりやすいコメント付きの明確なコードは、1、4、または12か月後にそのコードに戻る必要がある場合の命の恩人です。コードを読むと、コードが何をしているのかがわかります。コメントを読むと、コードが何をしているのかがわかります。
railsdog

82

バージョン管理は、おそらくあなたの支出に見合うだけの価値をもたらすでしょう。長期間の保管だけでなく、短期間の実験を追跡し、作業中の最後のバージョンに戻ってメモを保存するのに最適です。

次に有用なのは単体​​テストです。単体テストに関することは、数百万行のコードを含むコードベースでさえ、一度に1つの関数で単体テストされることです。単体テストは、抽象化の最低レベルで、小規模で行われます。つまり、小さなコードベース用に記述された単体テストと大規模なコードベース用に使用された単体テストとの間に基本的な違いはありません。それだけではありません。

単体テストは、他の何かを修正するときにすでに機能していたものを壊さないようにするか、少なくとも修正したときにすぐに伝えるための最良の方法です。プログラマーほど熟練していない場合、またはエラーが発生する可能性が低くなったり明白になったりするように構成されたより冗長なコードを記述する方法がわからない、またはしたくない場合に実際役立ちます。

バージョン管理と単体テストの作成の間で、コードは自然にきれいになります。プラトーに達したときに、よりクリーンなコーディングのための他のテクニックを学ぶことができます。


78
私は自分のコードの大部分を宗教的に単体テストしますが、探索的で科学的なコードの単体テストは役に立たないことがわかりました。この方法論は、基本的にここでは機能していないようです。分析コードを単体テストする私の分野の計算科学者は知りません。この不一致の理由がわからないが、理由の1つは確かに、些細なユニットを除いて、良いテストケースを確立することが難しいか不可能であるということです。
コンラッドルドルフ

22
@KonradRudolphそのような場合のコツは、明確に定義可能な動作(この入力を読んで、この値を計算)を持つコードの部分と、純粋に探索的であるか、または適応しているコードの部分との間の懸念を、人間が読み取れる出力または視覚化。ここでの問題は、懸念の分離が不十分であると、これらの行が不鮮明になり、このコンテキストでの単体テストが不可能であるという認識につながり、繰り返しのサイクルの最初に戻るということです。
Ant P

18
副次的な注意として、バージョン管理はLaTeX文書に対しても非常にうまく機能します。これは、この形式がテキストの差分を受け入れやすいためです。このようにして、論文とそれらをサポートするコードの両方のリポジトリを作成できます。Gitのような分散バージョン管理を検討することをお勧めします。学習曲線は少しありますが、理解すれば、開発を反復するためのすっきりとした方法が得られ、アカデミック向けの無料のチームアカウント提供する Githubのようなプラットフォームを使用する興味深いオプションがいくつかあります。
ダンブライアント

12
@AntP明確に定義されたテスト可能なユニットにリファクタリングできるコードがそれほど多くない可能性があります。科学的なコードの多くは、本質的に多くのライブラリを一緒にテーピングしています。これらのライブラリは既に十分にテストされ、きれいに構成されています。つまり、著者は「接着剤」を書くだけでよく、私の経験では、トートロジーではない接着剤の単体テストを書くことはほぼ不可能です。
James_pic

7
「バージョン管理と単体テストの作成の間で、コードは自然にずっときれいになります。」これは真実ではありません。個人的にそれを証明できます。これらのツールどちらも、安っぽいコードを書くことを妨げるものではありません。特に、安っぽいコードの上に安っぽいテストを書くと、クリーンアップがさらに難しくなります。テストは魔法のような特効薬ではありません。テストのように話すことは、まだ学んでいる開発者(全員)にとっては恐ろしいことです。ただし、バージョン管理は一般的に、悪いテストのようにコード自体に損傷を与えることはありません。
jpmc26

29

(バージョン管理を使用し、新しい反復ごとに新しいファイルを作成し、そのすべてをどこかにテキストファイルに記録することは別として、おそらく状況を劇的に改善するでしょう)

おそらく自分でこれを理解したでしょうが、「すべてをテキストファイルにすべて記録する」必要がある場合は、バージョン管理システムを最大限に活用していません。Subversion、git、またはMercurialなどを使用し、コミットごとに適切なコミットメッセージを書き込むと、テキストファイルの目的を果たしますが、リポジトリから分離できないログが作成されます。

それはともかく、バージョン管理を使用することは、既存の回答のいずれにも言及されていない理由:結果の再現性のためにできる最も重要なことです。ログメッセージを使用するか、リビジョン番号を使用して結果にメモを追加できる場合は、結果を再生成できることを確認でき、論文でコードを公開することができます。

より小さなコードを書くために単体テストは必要ですか?OOPはどうですか?大規模なプロジェクトで作業するのではなく、「科学的プログラミング」を行うときに、適切でクリーンなコードを迅速に記述するのにどのようなアプローチが適していますか?

ユニットテストは必ずしも必要ではありませんが、(a)コードがモジュール式であり、全体ではなくユニットをテストできる場合に役立ちます。(b)テストを作成できます。コードで生成するのではなく、予想される出力をコードで生成するのではなく、手動で書き込むことができれば理想的ですが、少なくともコードで生成すると、動作が変更されたかどうかを示す回帰テストが得られます。テストがテストしているコードよりもバグがある可能性が高いかどうかを検討してください。

OOPはツールです。役立つ場合は使用しますが、それだけがパラダイムではありません。手続き型プログラミングのみを本当に知っていると仮定します:その場合、説明したコンテキストでは、OOPよりも関数型プログラミングを勉強する方が有益であり、特に可能な限り副作用を回避することは有益だと思います。Pythonは非常に機能的なスタイルで記述できます。


4
コミットメッセージの場合は+1。それらは、実際に適用されたときにコードのバージョンに関連付けられているため、古くなることのないコメントのようなものです。古いコードを理解するには、古くなったコメントを読むよりも、プロジェクトの履歴を見るほうが簡単です(変更が妥当な粒度でコミットされている場合)。
愚かなフリーク

Subversion、git、およびMercurialは代替できません。Subversion経由のローカルリポジトリでGit(またはMercurial)を使用することを強く推奨します。単独のコーダーを使用すると、Subversionの欠陥はそれほど問題になりませんが、共同開発には最適なツールではなく、研究で発生する可能性があります
-mcottle

2
@mcottle、個人的にはgitが好きですが、特に選択は積極的な宗教戦争の1つであるため、これが違いについて詳しく説明するのに適した場所だとは思いませんでした。OPに何かを使うように勧める方が、エリアから追い払うよりも優れているため、どのような場合でも決定は永続的ではありません。
ピーターテイラー

21

大学院で、私は自分でアルゴリズムの重いコードを書きました。それは割れるのが少し難しいです。大雑把に言えば、多くのプログラミング規約は、情報をデータベースに入れ、適切なタイミングでそれを取得し、そのデータをマッサージしてユーザーに提示するという考え方を中心に構築されています。そのプロセスのアルゴリズムが多い部分。これらのプログラムについては、OOPについて聞いたこと、コードを短い関数に分割すること、すべてを一目で簡単に理解できるようにすることはすべて、優れたアドバイスです。しかし、アルゴリズムが重いコードや、複雑な数学的計算などを実装するコードではまったく機能しません。

科学的な計算を実行するスクリプトを作成している場合、使用する方程式やアルゴリズムが書かれた論文をお持ちでしょう。自分で発見した新しいアイデアを使用している場合は、自分の論文でそれらを公開することを期待しています。この場合、ルールは次のとおりです。コードは、公開されている式と可能な限り同じように読み取ります。以下にSoftware Engineering.SEの回答を示します。200人以上の賛成票がこのアプローチを支持し、どのように見えるかを説明しています。短い変数名の言い訳はありますか?

別の例として、物理学の研究とエンジニアリングに使用される物理シミュレーションツールであるSimbodyには、すばらしいコードのスニペットがいくつかあります。これらのスニペットには、計算に使用されている方程式を示すコメントがあり、その後に、実装されている方程式に可能な限り近いコードが続きます。

ContactGeometry.cpp

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

ContactGeometry_Sphere.cpp

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

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

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

9
さらに、「公開された方程式にできるだけ似たコードを読み取る」コードを作成します。申し訳ありませんが、長く意味のある変数名を支持しています。科学的コードで最も意味のある名前は、多くの場合、コードが実装しようとしている科学ジャーナルの論文で使用されている正確な規則であるため、厄介で、短く、野bruです。ジャーナルの論文で見つかった方程式を実装する方程式が大量のコードのチャンクの場合、論文の命名法に可能な限り近いことをお勧めしますが、これが良いコーディング標準のグレインに反する場合は難しいです。
デビッドハンメン

@DavidHammen:大学院生として、私はそれを尊重します。プログラマとして、私はあなたが各関数の上部に巨大なコメントブロックを置いて、単なる一時的なプレースホルダーであっても、各変数が何を表しているのかを平易な英語(または選択した言語)で説明することを主張します。そうすれば、少なくとも振り返る参照があります。
-tonysdg

1
@DavidHammenまた、ソースファイルでUTF-8と変数名のための単純なルールのためのPythonのサポートは、それが簡単に宣言することができますλφ醜いのではなく、lambda_phy...
マティアス・エッティンガー

1
@tonysdg既に参照があります。「Hammen、et al。(2018)」(またはその他)と呼ばれます。変数の意味を、これまでのコメントブロックよりもはるかに詳細に説明します。変数名を論文の表記に近づける理由は、論文の内容をコードの内容に簡単に結び付けるためです。
誰も

17

ですから、私の仕事はカリフォルニア大学システムの研究データの公開と保存です。数人の人々が再現性について言及していますが、それが本当に重要な問題だと思います:誰かがあなたの実験を再現するために必要なものを文書化する方法でコードを文書化し、理想的には、他の誰かのためにそれを簡単にするコードを書く実験を再現し、エラーの原因について結果を確認します。

しかし、私が言及していないこと、重要だと思うことは、資金提供機関がソフトウェア公開をデータ公開の一部として、ソフトウェア公開をオープンサイエンスの要件にすることをますます重視していることです。

そのため、一般的なソフトウェア開発者ではなく研究者を対象とした具体的なものが必要な場合、Software Carpentryの組織を十分に推奨することはできません。あなたが彼らのワークショップのいずれかに参加できるなら、素晴らしい。科学的コンピューティングのベストプラクティスに関する論文の一部を読むだけで十分な時間/アクセスがあれば、それも良いことです。後者から:

科学者は通常、これらの目的のために独自のソフトウェアを開発します。そのためには、ドメイン固有のかなりの知識が必要です。その結果、最近の研究により、科学者は通常、ソフトウェアの開発に30%以上の時間を費やしていることがわかりました。ただし、それらの90%以上は主に独学であるため、メンテナンス可能なコードの作成、バージョン管理と問題追跡の使用、コードレビュー、単体テスト、タスクの自動化などの基本的なソフトウェア開発プラクティスにさらされていません。

ソフトウェアは単なる実験装置の一種であり、物理的な装置と同じように慎重に構築、確認、使用する必要があると考えています。ただし、ほとんどの科学者は研究室やフィールド機器の検証に注意を払っていますが、ほとんどの科学者はソフトウェアの信頼性を知りません。これは、公開された研究の中心的な結論に影響を与える重大なエラーにつながる可能性があります。…

また、ソフトウェアは複数のプロジェクトで使用されることが多く、他の科学者によって再利用されることが多いため、計算エラーは科学プロセスに不釣り合いな影響を与える可能性があります。別のグループのコードからのエラーが公開後まで発見されなかった場合、この種のカスケードの影響により、いくつかの顕著な撤回が引き起こされました。

推奨するプラクティスの概要:

  1. コンピューターではなく人々のためにプログラムを書く
  2. コンピューターに仕事をさせる
  3. 増分変更を行う
  4. 自分(または他の人)を繰り返さないでください
  5. 間違いを計画する
  6. 正しく動作した後にのみソフトウェアを最適化する
  7. メカニズムではなく、設計と目的を文書化する
  8. 協力する

論文では、これらの各点についてかなり詳細に説明しています。


16

いくつかの標準的なことを実行できたときにOOPと呼ばれるコードを書くことは本当に意味がありますか?

個人的な答え:
私は科学的な目的のためにも多くのスクリプトを作成します。小さいスクリプトの場合は、一般的な適切なプログラミングの実践(つまり、バージョン管理の使用、変数名を使用した自己管理の実践)に従うだけです。データセットをすばやく開いたり視覚化したりするために何かを書いているだけなら、OOPに煩わされることはありません。

一般的な答え:
「それは依存します。」しかし、プログラミングの概念またはパラダイムをいつ使用するかを理解するのに苦労している場合は、次のことを考慮する必要があります。

  • スケーラビリティ:スクリプトはスタンドアロンになりますか、それとも最終的にはより大きなプログラムで使用されますか?もしそうなら、OOPを使用した大規模なプログラミングですか?スクリプト内のコードをより大きなプログラムに簡単に統合できますか?
  • モジュール性:一般的に、コードはモジュール化する必要があります。ただし、OOPはコードを非常に特別な方法でチャンクに分割します。そのタイプのモジュール性(つまり、スクリプトをクラスに分割すること)は、あなたがしていることに対して意味がありますか?

これらの小さくて高速なプロジェクトで「やり直す」方法ときれいにプログラムする方法を知りたいです。

#1:そこにあるものに慣れる:
あなたが「ちょうど」スクリプトを書いている(そして、あなたは本当に科学のコンポーネントを気にしている)にもかかわらず、あなたは異なるプログラミングの概念とパラダイムについて学ぶのに時間をかけるべきです。そうすることで、何を使用すべきか、使用すべきではないか、いつ使用すべきかをよりよく把握できます。それは少し気難しいかもしれません。そして、あなたはまだ「どこから始めますか/何を見始めますか?」という質問があるかもしれません。次の2つの箇条書きで、適切な出発点を説明しようとします。

#2:あなたが間違っているとわかっていることの修正を開始します。
個人的には、間違っているとわかっていることから始めます。いくつかのバージョン管理を取得し、それらの変数名をより良くするために自分自身を訓練し始めます(それは深刻な闘争です)。あなたが間違っているとわかっていることを修正することは明白に聞こえるかもしれません。しかし、私の経験では、1つのことを修正することで他の何かにつながるなどのことがわかりました。それを知る前に、私が間違っていた10の異なることを明らかにし、それらを修正する方法またはそれらをクリーンな方法で実装する方法を見つけました。

#3:プログラミングパートナーを取得する:
正式なクラスを受講する必要がない場合は、開発者とチームを組み、コードのレビューを依頼することを検討してください。彼らがあなたがしていることの科学の部分を理解していなくても、彼らはあなたのコードをよりエレガントにするためにあなたができたことをあなたに伝えることができるかもしれません。

#4:コンソーシアムを探す:
自分がどの科学分野にいるのかわかりません。しかし、科学の世界で何をしているのかによっては、コンソーシアム、ワーキンググループ、会議参加者を探してみてください。次に、作業中の標準があるかどうかを確認します。それは、いくつかのコーディング標準につながる可能性があります。たとえば、私は多くの地理空間の仕事をしています。会議の論文やワーキンググループを見ると、私はOpen Geospatial Consortiumにたどり着きました。彼らが行うことの1つは、地理空間開発の標準に関する作業です。

それがお役に立てば幸いです!


補足:OOPを例として使用したことを知っています。OOPを使用してコードの記述を処理する方法だけにこだわっていると思わせたくありませんでした。その例を続けて答えを書くほうが簡単でした。


#3が最も重要な問題だと思います-経験豊富なプログラマーはOPに必要な概念(#1)、より良い方法でスクリプトを整理する方法、バージョン管理の使用方法(#2)をOPに伝えることができます。
Doc Brown

16

私はUnixの原則に固執することをお勧めします。(キッス)

または、別の言い方をすれば、一度に1つのことを実行し、それをうまく実行します。

どういう意味ですか?まあ、まず第一に、それはあなたの機能が短いはずであることを意味します。数秒以内に目的、使用法、実装を完全に理解できない機能は、間違いなく長すぎます。いくつかのことを一度に行う可能性があり、それぞれが独自の機能でなければなりません。分割します。

コード行の観点から、私の経験則では、10行が適切な機能であり、20行を超えるものはほとんどがらくたです。他の人には他の発見的手法があります。重要な部分は、実際に瞬時に把握できる長さに抑えることです。

長い関数をどのように分割しますか?さて、最初にコードの繰り返しパターンを探します。そして、あなたはアウト要因それらに名前を付け、これらのコードパターンを、そしてあなたのコードを見てシュリンクを。本当に、最高のリファクタリングはコードサイズを削減するリファクタリングです。

これは、問題の機能がコピーアンドペーストでプログラムされている場合に特に当てはまります。このような繰り返されるパターンを見たときはいつでも、これがおそらく独自の機能に変わる可能性があることをすぐに知っています。これが、Do n't Repeat Yourself(DRY)の原則です。コピーと貼り付けを行うたびに、何か間違ったことをしていることになります!代わりに関数を作成してください。

逸話
私はかつて数ヶ月かけて、それぞれ約500行の機能を持つコードをリファクタリングしました。完了後、合計コードは約1000行短くなりました。コードの行数に関しては、マイナスの出力を生成していました。私は会社を借りていました(http://www.geekherocomic.com/2008/10/09/programmers-salary-policy/index.html)。それでも、これは私がこれまでにやった中で最も価値のある作品の一つだと固く信じています...

一部の機能は、いくつかの異なる処理を次々に実行しているため、長くなる場合があります。これらはDRY違反ではありませんが、分割することもできます。その結果、多くの場合、元の関数の個々のステップを実装する一連の関数を呼び出す高レベル関数が作成されます。これは一般にコードサイズを増加させますが、追加された関数名はコードをより読みやすくするのに役立ちます。これは、すべてのステップに明示的に名前が付けられたトップレベル関数があるからです。また、この分割後、どのステップがどのデータに作用するかが明確になります。(関数の引数。グローバル変数を使用しませんか?)

この種のセクション関数分割の優れたヒューリスティックは、セクションコメントを記述したいとき、またはコード内でセクションコメントを見つけたときです。これはおそらく、関数を分割する必要があるポイントの1つです。セクションコメントは、新しい関数の名前を示すのにも役立ちます。

KISSとDRYの原則は、あなたを大きく前進させることができます。すぐにOOPなどで始める必要はありません。多くの場合、これら2つを適用するだけで大​​幅な簡素化を実現できます。ただし、プログラムコードをより明確にするために使用できる追加のツールを提供するため、OOPやその他のパラダイムについて知ることは、長期的に見れば見返りがあります。

最後に、コミットですべてのアクションを記録します。あなたは何かを新しい関数に分解します。それがcommitです。2つの機能を1つに融合します。これらは実際には同じことを行うため、コミットです。変数の名前を変更する場合、それはcommitです。頻繁にコミットします。1日が過ぎても、コミットしなかった場合は、何か間違ったことをした可能性があります。


2
長いメソッドの分割に関する大きなポイント。逸話の後の最初の段落に関する別の優れたヒューリスティック:メソッドを論理的にセクションに分割でき、各セクションが何をするかを説明するコメントを書きたい場合は、コメントで分割する必要があります。幸いなことに、これらのコメントはおそらく新しいメソッドを何と呼ぶべきかについての良いアイデアを与えてくれるでしょう。
ジャックス

@Jaquez Ah、それを完全に忘れてしまった。思い出させてくれてありがとう。これを含むように回答を更新しました:
cmaster

1
素晴らしい点は、これを単純化して、「DRY」が最も重要な単一の要因であると言うことです。「繰り返し」を特定して削除することは、他のほぼすべてのプログラミング構成の基礎です。別の言い方をすれば、DRYコードの作成を支援するために、少なくとも部分的にすべてのプログラミング構造があります。「No Duplication Ever」と言って開始し、それを特定して排除する練習をします。重複する可能性のあるものについては、非常にオープンです
Bill K

11

私は、バージョン管理があなたの問題の多くをすぐに解決することを他の人に同意します。具体的には:

  • どのバージョンの変更が行われたかのリストを保持したり、ファイルのコピーをたくさん持ったりする必要はありません。バージョン管理がそれを処理するからです。
  • 上書きなどによりファイルが失われることはありません(基本に固執している限り。たとえば、「履歴の書き換え」は避けてください)
  • 古くなったコメントやデッドコードなどを「念のために」保持する必要はありません。バージョン管理に取り組んだら、遠慮なくそれらを破棄してください。これは非常に解放感があります!

考えすぎないでください。gitを使用してください。単純なコマンド(たとえば、単一のmasterブランチ)に固執し、おそらくGUIを使用すれば、問題はありません。ボーナスとして、無料の公開とバックアップのためにgitlab、githubなどを使用できます;)

私がこの答えを書いた理由は、上で言及したことのない、あなたが試みるかもしれない2つのことに取り組むことでした。1つは、単体テストの軽量な代替手段としてアサーションを使用することです。単体テストは、機能/モジュール/テスト対象の「外部」に置かれる傾向があります。通常は、機能にデータを送信し、結果を受信して​​、その結果のプロパティを確認します。これは一般に良い考えですが、いくつかの理由で不便かもしれません(特に「捨てる」コードの場合):

  • 単体テストでは、関数に渡すデータを決定する必要があります。そのデータは現実的でなければならず(そうでなければテストするポイントはほとんどありません)、正しい形式などでなければなりません。
  • 単体テストは、アサートするものに「アクセス」する必要があります。特に、単体テストでは関数内の中間データをチェックできません。その機能を小さな断片に分割し、それらの断片をテストし、別の場所に接続する必要があります。
  • 単体テストもプログラムに関連すると想定されます。たとえば、テストスイートは、最後に実行されてから大きな変更があった場合に "古くなった"状態になる可能性があり、使用されなくなったコードのテストも多数ある場合があります。

アサーションには、プログラムの通常の実行中にチェックされるため、これらの欠点はありません。特に:

  • これらは通常のプログラム実行の一部として実行されるため、実際に使用する実際のデータがあります。これは個別のキュレーションを必要とせず、(定義により)現実的であり、正しい形式です。
  • アサーションはコードのどこにでも書くことができるので、チェックしたいデータにアクセスできるところならどこにでもアサーションを置くことができます。関数の中間値をテストする場合は、その関数の中間にいくつかのアサーションを配置するだけです!
  • インラインで記述されているため、アサーションはコードの構造と「同期していません」。アサーションがデフォルトでチェックされていることを確認すれば、次にプログラムを実行するときにアサーションがパスするかどうかすぐにわかるので、アサーションが「古くなる」ことを心配する必要もありません。

速度として要因に言及しますが、その場合、アサーションチェックはそのループでは望ましくない可能性があります(ただし、セットアップおよび後続の処理のチェックには依然として役立ちます)。ただし、アサーションのほとんどすべての実装は、アサーションをオフにする方法を提供します。たとえばPythonでは-Oオプションを使用して実行することで無効にできます(以前はアサーションを無効にする必要性を感じたことがないため、これは知りませんでした)。そのままにしおくことをお勧めしますデフォルトでは コーディング/デバッグ/テストのサイクルが遅くなった場合は、データのサブセットを小さくしてテストするか、テスト中にシミュレーションの反復回数を少なくするか、何でもいいでしょう。パフォーマンス上の理由でテスト以外の実行でアサーションを無効にした場合、最初に行うことをお勧めすることは、それらが実際にスローダウンの原因であるかどうかを測定することです!(パフォーマンスのボトルネックになると、自分を欺くのは非常に簡単です)

私の最後のアドバイスは、依存関係を管理するビルドシステムを使用することです。私は個人的にこれのためにNixを使用していますが、Guixについても良いことを聞いています。また、Dockerのような代替手段もあります。これは、科学的な観点からはそれほど有用ではありませんが、おそらくもう少し馴染みがあるでしょう。

Nixのようなシステムはごく最近(少し)人気が出てきており、あなたが説明しているように「捨てる」コードではやり過ぎだと考える人もいるかもしれませんが、科学計算の再現性に対する利点は膨大です。次のような実験を実行するためのシェルスクリプトを検討してください(例run.sh

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

代わりに、次のようにNixの「派生」に書き換えることができます(例:)run.nix

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

間のもの''...''は、以前と同じbashコードです。ただし${...}、他の文字列のコンテンツを「スプライス」するために使用できることを除きます(この場合./.、を含むディレクトリのパスに展開されますrun.nix)。このwith import ...行は、bashコードの実行を提供するNixの標準ライブラリをインポートしrunCommandます。を使用して実験を実行できます。これによりnix-build run.nix、などのパスが得られます/nix/store/1wv437qdjg6j171gjanj5fvg5kxc828p-output.csv

それで、これは何を私たちに買うのでしょうか?Nixは「クリーン」な環境を自動的に設定します。これは、明示的に要求したものにのみアクセスできます。特に、$HOMEインストールしたシステムソフトウェアなどの変数にはアクセスできません。これにより、~/.configインストールしたプログラムの内容やバージョンなど、現在のマシンの詳細に依存しない結果が得られます。他の人が結果を複製するのを防ぐものです。これが私がそれを追加した理由ですcpコマンドは、デフォルトではプロジェクトにアクセスできないためです。システムのソフトウェアがNixスクリプトで使用できないのは面倒に思えるかもしれませんが、逆の場合もあります。スクリプトで使用するためにシステム(Nix以外)にインストールする必要はありません。Nixはそれを要求するだけで、Nixは必要なものを取り出してコンパイル/コンパイルします(ほとんどのものはバイナリとしてダウンロードされます。標準ライブラリも巨大です!)。たとえば、特定のPythonおよびHaskellパッケージの束が必要な場合、それらの言語の特定のバージョンに加えて、他のジャンクも必要です(理由はありません)。

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

同じnix-build run.nixことがこれを実行し、最初に要求したすべてをフェッチします(後で必要に応じてすべてキャッシュします)。出力(と呼ばれるファイル/ディレクトリ$out)は、Nixによって保存されます。これは、Nixが出力するパスです。要求されたすべての入力(スクリプトの内容、他のパッケージ、名前、コンパイラフラグなど)の暗号化ハッシュによって識別されます。これらの他のパッケージは入力のハッシュなどによって識別されるため、bashをコンパイルしたGCCのバージョンをコンパイルしたGCCのバージョンに戻るなど、あらゆるものに完全なチェーンがあります。

これが科学的コードのために私たちに多くのことを買ってくれて、それから始めるのが合理的に簡単であることを願っています。また、科学者に非常に真剣に受け止められ始めています。たとえば、(Googleのトップヒット)https://dl.acm.org/citation.cfm?id=2830172ですから、(プログラミングのように)育成する価値のあるスキルかもしれません


2
非常に詳細な有用な回答-私は他の回答が本当に好きですが、アサーションは非常に有用な最初のステップのように聞こえます。
ヘザー

9

本格的なバージョン管理+パッケージ+単体テストのような考え方(これは、ある時点で達成すべき優れたプログラミング手法です)に進むことなく、私が適合すると思う中間ソリューションの1つはJupiter Notebookを使用することです。これは、科学計算とよりよく統合するようです。

コードに考えを混ぜることができるという利点があります。あるアプローチが他のアプローチより優れている理由を説明し、古いコードをそのままアドホックセクションに残します。セルを適切に使用するだけでなく、当然コードを断片化し、理解に役立つ機能に整理するように導きます。


1
さらに、これは本当に再現性に役立ちます。たとえば、まったく同じコードを実行して出版図を生成したり、数か月前に整理したものに戻ってレビュー担当者のコメントを組み込むことができます。
afaulconbridge

もっと読みたい人のために、これは文学的プログラミングとしても知られています。
llrs

6

一番の答えはすでに良いですが、私はあなたの質問のいくつかに直接対処したかったです。

より小さなコードを書くために単体テストは必要ですか?

コードのサイズは、単体テストの必要性に直接関係していません。間接的に関連しています。単体テストは複雑なコードベースではより価値があり、小さなコードベースは一般的に大きなコードベースほど複雑ではありません。

ユニットテストは、ミスを犯しやすいコードや、このコードの多くの実装を行うコードに適しています。単体テストは、現在の開発を支援することはほとんどありませんが、将来の間違いを防止するために、既存のコードが突然動作しないようにするために多くのことを行います(あなたがそのことに触れなかったとしても)。

ライブラリAが数値の2乗を実行し、ライブラリBがピタゴラスの定理を適用するアプリケーションがあるとします。明らかに、BはAに依存します。ライブラリAで何かを修正する必要があります。また、数値を2乗せずに立方体にするバグを導入したとしましょう。

ライブラリBは、突然例外をスローしたり、単に間違った出力を与えたりする可能性があり、突然動作を開始します。また、ライブラリBのバージョン履歴を見ると、そのままであることがわかります。問題のある最終結果は、何が間違っている可能性があるかを示していないことであり、問​​題がAにあることに気付く前にBの動作をデバッグする必要があります。これは無駄な努力です。

単体テストを入力します。これらのテストにより、ライブラリAが意図したとおりに機能していることが確認されます。ライブラリAにバグを導入して、悪い結果を返す場合は、ユニットテストでそれをキャッチします。したがって、ライブラリBをデバッグしようとして立ち往生することはありません。
これは範囲を超えていますが、継続的な統合開発では、誰かがコードをコミットするたびにユニットテストが実行されます。

特に複雑な数学演算の場合、単体テストは祝福になる可能性があります。いくつかの計算例を実行してから、計算された出力と実際の出力を(同じ入力パラメーターに基づいて)比較する単体テストを作成します。

ただし、単体テストは優れたコードの作成には役立ちませんが、維持することに注意してください。通常、コードを一度書いて、それを再訪しない場合、単体テストはあまり有益ではありません。

OOPはどうですか?

OOPは、個別のエンティティに関する考え方です。たとえば、次のとおりです。

Customerがを購入したいときProduct、彼はVendorを受信するように話しOrderます。Accountantその後、お支払いいただきますVendor

これを、機能プログラマが物事についてどのように考えているかと比較してください。

顧客がしたいときpurchaseProduct()、彼はtalktoVendor()そうするでしょsendOrder()う。会計士はそれからpayVendor()

リンゴとオレンジ。どちらも客観的に他より優れているわけではありません。注意すべき興味深い点の1つは、OOPについてVendorは2回言及されていますが、同じことを指しているということです。しかし、関数型プログラミングのための、talktoVendor()そしてpayVendor()二つの別々のものです。
これは、アプローチの違いを示しています。これら2つのアクションの間にベンダー固有の共有ロジックが多数ある場合、OOPはコードの重複を減らします。ただし、2つの間に共有ロジックがない場合、それらを1つにマージするのVendorは無駄な作業です(したがって、関数型プログラミングの方が効率的です)。

多くの場合、数学と科学の計算は、暗黙の共有ロジック/式に依存しない別個の操作です。そのため、関数型プログラミングはOOPよりも頻繁に使用されます。

大規模なプロジェクトで作業するのではなく、「科学的プログラミング」を行うときに、適切でクリーンなコードを迅速に記述するのにどのようなアプローチが適していますか?

あなたの質問は、「良い、きれいなコード」の定義は、あなたが科学的なプログラミングをしているのか、大規模な(エンタープライズを意味していると思います)プロジェクトをしているのかによって変わります。

良いコードの定義は変わりません。複雑さを回避する必要性(クリーンなコードを書くことによって行うことができる)、しかし、変更を行います。

同じ議論がここに戻ってきます。

  • 古いコードを再検討せずに、区画化する必要なくロジックを完全に理解している場合、物事を保守可能にするために過度の努力を費やさないでください。
  • 古いコードを再検討するか、必要なロジックが複雑すぎて一度に取り組むことができない場合(したがって、ソリューションを区分化する必要があります)、クリーンで再利用可能なクローズの記述に集中してください。

多くの場合、プログラミング自体はそれほど複雑ではないため、これらの質問をします。私がプログラミングでテストまたは研究しているのは、数学または科学に関するものです。

私はあなたがここで作っているの区別を得るが、あなたがたときに振り返って、既存のコードでは、あなたは数学とプログラミングの両方を見ています。場合はどちらかが不自然または複雑で、あなたはそれを読むのに苦労ます。

たとえば、2つの変数と関数がおそらくそれを処理できる場合、クラスは必要ですか?

OOPの原則はさておき、いくつかのデータ値を格納するクラスを作成する主な理由は、メソッドパラメーターと戻り値の宣言を単純化するためです。たとえば、場所(緯度/経度のペア)を使用するメソッドがたくさんある場合、すぐに入力float latitude, float longitudeするのにうんざりして、を書くことを好みますLocation loc

これは、メソッドが通常1つの値を返す(言語固有の機能がより多くの値を返す場合を除いて)ことを考えるとさらに複雑になり、場所のようなものは2つの値(lat + lon)を返します。これにより、Locationクラスを作成してコードを簡素化することができます。

たとえば、2つの変数と関数がおそらくそれを処理できる場合、クラスは必要ですか?

注意すべきもう1つの興味深い点は、データ値とメソッドを混在させずにOOPを使用できることです。すべての開発者がここで同意するわけではありません(アンチパターンと呼ばれる人もいます)が、個別のデータクラス(値フィールドを保存)とロジッククラス(メソッドを保存)がある貧弱なデータモデルを持つことができます。
もちろん、これはスペクトル上です。あなたは完全に貧血である必要はありません、あなたがそれが適切だと思うとき、あなたはそれを使うことができます。

たとえば、人の名と姓を単純に連結するメソッドは、Person実際には「ロジック」ではなく計算値であるため、クラス自体に格納できます。

(これらは一般に、プログラムの速度が速いほうが望ましい状況であると考えてください-シミュレーションの25,000,000以上のタイムステップを実行しているとき、それを望んでいます。)

クラスは常にそのフィールドの合計と同じ大きさです。Location2つのfloat値で構成される例をもう一度Location見てみましょう。1つのオブジェクトが2つの別々のfloat値と同じくらいのメモリを占有することに注意してください。

その意味では、OOPを使用しているかどうかは関係ありません。メモリフットプリントは同じです。

パフォーマンス自体もまた、大きなハードルではありません。たとえば、グローバルメソッドとクラスメソッドの使用の違いは、実行時のパフォーマンスとは関係ありませんが、バイトコードのコンパイル時の生成と関係があります。

このように考えてください。ケーキのレシピを英語で書くかスペイン語で書くかによって、ケーキの焼き付けに30分かかるという事実は変わりません(=実行時のパフォーマンス)。レシピの言語が変わるのは、料理人がどのように材料を混ぜるか(=バイトコードのコンパイル)だけです。

特にPythonの場合、呼び出す前にコードを明示的にプリコンパイルする必要はありません。ただし、プリコンパイルしない場合、コードを実行しようとするとコンパイルが発生します。「ランタイム」と言うときは、実行に先行するコンパイルではなく、実行そのものを意味します。


6

クリーンな科学的コードの利点

  • ...プログラミングの本を見ると、大規模なプロジェクトで対処されているようです。

  • ...いくつかの標準的なことを実行できた場合、OOPと呼ばれるコードを書くことは本当に意味がありますか?

将来のコーダーの観点からコードを検討すると役立つ場合があります。

  • なぜ彼らはこのファイルを開いたのですか?
  • 彼らは何を探していますか?

私の経験から、

クリーンなコードを使用すると、結果を簡単に確認できるはずです。

  • ユーザーがプログラムを実行するために必要なことを正確に把握できるようにします。
  • 個々のアルゴリズムを個別にベンチマークできるように、プログラムを分割することもできます。

  • 1つの無関係な操作が別の操作の動作を変える、直感に反する副作用のある関数を作成しないでください。避けられない場合は、コードに必要なものとそのセットアップ方法を文書化します。

クリーンコードは、将来のコーダーのサンプルコードとして使用できます。

明確なコメント(関数の呼び出し方法を示すものを含む)と適切に分離された関数は、誰かがあなたの仕事から有用なものを作るために始めたばかりの(または将来のあなた)にかかる時間に大きな違いをもたらします。

これに加えて、アルゴリズムに実際の「API」を作成すると、他の人が使用できるようにスクリプトを実際のライブラリにすることを決定した場合の準備が整います。

推奨事項

コメントを使用して数式を「引用」します。

  • 特に「最適化」(トリガーID、テイラー級数など)を使用した場合は、「引用」数式にコメントを追加します。
  • 本から公式を入手した場合John Smith Method from Some Book 1st Ed. Section 1.2.3 Pg 180、ウェブサイト上または論文で公式を見つけた場合、同様に引用してコメントを追加します。
  • 「リンクのみ」のコメントは避けることをお勧めします。ユーザーがグーグルで検索できるように、必ず名前でメソッドを参照してください。古い内部ページにリダイレクトされる「リンクのみ」のコメントに遭遇しました。
  • ユニコード/ ASCIIで読みやすい場合は、コメントに数式を入力できますが、これは非常に厄介になる可能性があります(コードコメントはLaTeXではありません)。

コメントを賢く使う

適切な変数名/関数名を使用してコードの可読性を改善できる場合は、最初にそれを実行してください。コメントは削除するまで永久に残るので、期限切れにならないコメントを作成してください。

わかりやすい変数名を使用する

  • 1文字の変数は、式の一部である場合に最適なオプションです。
  • 将来の読者があなたが書いたコードを見て、それをあなたが実装している方程式と比較できるようにすることが重要かもしれません。
  • 必要に応じて、たとえば接尾辞を追加して、その実際の意味を説明することを検討してください。 xBar_AverageVelocity
  • 前述したように、コメントのどこかに、使用している式/方法を名前で明確に示すことをお勧めします。

既知の良好なデータおよび既知の不良データに対してプログラムを実行するコードを記述します。

より小さなコードを書くために単体テストは必要ですか?

ユニットテストは役立つと思います。科学コードのユニットテストの最良の形態は、既知の悪いデータと良いデータで実行される一連のテストだと思います。

アルゴリズムを実行するコードを記述し、結果が予想とどの程度逸脱しているかを確認します。これにより、誤って誤検知の原因となるものをハードコーディングしたり、関数が常に同じ値を返すようなミスを犯したりする(潜在的に非常に悪く、見つけにくい)問題を見つけるのに役立ちます。

これは、任意の抽象化レベルで実行できることに注意してください。たとえば、パターンマッチングアルゴリズム全体をテストしたり、最適化プロセスの2つの結果間の距離を計算するだけの関数をテストしたりできます。最初に結果に最も重要な領域、および/または最も懸念しているコードの部分から始めます。

新しいテストケースを簡単に追加し、「ヘルパー」関数を追加することを検討し、入力データを効果的に構造化します。これは、入力データをファイルに保存してテストを簡単に再実行できるようにすることを意味する場合がありますが、誤検知や偏った/ささいな解決済みのテストケースを避けるよう非常に注意してください。

相互検証のようなものの使用を検討してください。詳細については、相互検証に関するこの投稿を参照してください。

バージョン管理を使用する

バージョン管理を使用して、外部サイトでリポジトリをホストすることをお勧めします。リポジトリを無料でホストするサイトがあります。

利点:

  1. ハードディスクが故障した場合のバックアップを提供します
  2. 履歴が提供されるため、最近発生した問題が誤ってファイルを変更したことが原因であるかどうかなど、心配する必要がなくなります。
  3. ブランチを使用することができます。これは、無関係な作業に影響を与えることなく、長期的/実験的なコードで作業するための良い方法です。

コードをコピー/貼り付けするときは注意してください

私のコードのスタイルは複雑で、何かを行う別の方法を記した古いコメントや、コピーされたコード行で満たされています。

  • コードのコピー/貼り付けは時間の節約になりますが、特に自分で書いていないコード(同僚のコードなど)の場合、それは最も危険なことの1つです。

  • コードを機能させてテストしたら、すぐに注意深く調べて、変数の名前を変更したり、理解できないものをコメントしたりすることをお勧めします。



6

取引のツールは通常、ニーズを解決するために発明されました。このツールを使用する必要がある場合は、使用する必要はありません。

具体的には、科学プログラムは最終目標ではなく、手段です。現在抱えている問題を解決するためにプログラムを作成します。そのプログラムが他の人によって使用される(そして維持される必要がある)のは10年後ではないでしょう。それだけでは、現在の開発者がバージョン管理などの他のユーザーの履歴を記録したり、単体テストなどのコードで機能をキャプチャしたりするツールを考慮する必要がないことを意味します。

その場合、何があなたに利益をもたらすでしょうか?

  • バージョン管理は、作業を非常に簡単にバックアップできるため便利です。2018年現在、githubはそうするのに非常に人気のある場所です(必要に応じて後でいつでも移動できます-gitは非常に柔軟です)。バックアップの安価で簡単な代替手段は、オペレーティングシステム(MacのTime Machine、Linuxのrsyncなど)での自動バックアップ手順です。コードは複数の場所に配置する必要があります!
  • ユニットテストは、最初に記述した場合、コードが実際に行うことを確認する方法を考えることを余儀なくされるため、コードに役立つAPIを設計するのに役立ちます。これは、後で再利用するコードを作成する場合に役立ち、これらのケースで機能することがわかっているため、アルゴリズムを変更する際に役立ちます。
  • ドキュメンテーション。使用するプログラミング言語(たとえばJavaのjavadoc)で適切なドキュメントを書くことを学んでください。未来のために書いてください。このプロセスでは、適切な変数名により文書化が容易になることがわかります。繰り返します。詩人が詩に対して行うのと同じ注意をドキュメントに与えます。
  • 優れたツールを使用してください。役立つ IDEを見つけて、よく学習してください。この方法では、変数の名前をより良い名前に変更するなどのリファクタリングがはるかに簡単です。
  • ピアレビューがある場合は、ピアレビューの使用を検討してください。部外者にコードを見て理解してもらうことは、あなたが書く未来の今現在のバージョンです。ピアがあなたのコードを理解していない場合、おそらくあなたも後で理解しないでしょう。

この回答はどうして賛成票を受け取らなかったのですか?今あります。私たちのグループは、ピアレビューが最も効果的なツールの1つであり、科学的コードに関しては単体テストよりもはるかに重要であることを発見しました。科学雑誌の記事にある複雑な方程式のセットをコードに翻訳すると、エラーが発生しやすくなります。科学者とエンジニアはしばしば非常に貧しいプログラマーを支援します。ピアレビューは、コードの維持/理解/使用を困難にするアーキテクチャのugさをキャッチできます。
デビッドハンメン

5

ここにすでにある良いアドバイスに加えて、あなたはあなたのプログラミングの目的、そしてあなたにとって重要なものを考慮したいかもしれません。

「私がプログラミングでテストまたは研究しているのは、数学または科学についてです。」

目的があなた自身の理解のために何かを実験してテストすることであり、結果がどうあるべきかを知っているなら、コードは基本的にはすぐに捨てられ、現在のアプローチで十分かもしれませんが、改善される可能性があります。結果が期待どおりでない場合は、戻って確認できます。

ただし、コーディングの結果が研究の方向性を示しており、結果がどうあるべきかわからない場合は、正確さが特に重要になります。コードにエラーがあると、実験から間違った結論を導き出し、研究全体にさまざまな悪い影響を与える可能性があります。

その場合、単体テストでコードを簡単に理解できる検証可能な関数に分割すると、より強固な構築ブリックが得られ、結果に自信が持てるようになり、後で多くのフラストレーションを解消できます。


5

バージョン管理と単体テストは、コード全体を整理して機能させるためのものですが、どちらも実際にはきれいなコードを書くのに役立ちません。

  • バージョン管理により、コードがいつどのように乱雑になったかを確認できます。
  • 単体テストは、コードが完全に混乱しているにもかかわらず、動作することを確認します。

厄介なコードを書くのを止めたい場合は、混乱が発生する場所で動作するツールが必要です。コードを書いているときです。よく使用されるツールはリンターと呼ばれます。私はpython開発者ではありませんが、Pylintが良い選択肢のようです。

リンターはあなたが書いたコードを見て、それを設定可能なベストプラクティスのセットと比較します。リンターに、変数がでなければならないというルールがありcamelCase、に1つ書くとsnake_case、それは間違いとしてフラグが立てられます。適切なリンターには、「宣言された変数を使用する必要がある」から「関数の循環的複雑度が3未満でなければならない」までのルールがあります。

ほとんどのコードエディターは、保存するたびに、または一般的に入力時にlinterを実行し、インラインで問題を示すように構成できます。のようx = 7に入力するxと、が強調表示され、より長い、より適切な名前を使用するように指示されます(構成済みの場合)。これは、ほとんどのワードプロセッサのスペルチェックのように機能し、無視するのが難しくなり、より良い習慣を構築するのに役立ちます。


これにはもっと多くの賛成票が必要です。+1
ヘザー

2
しかし、天国のために、リンターを好きなスタイルに設定する方法を知っていることを確認してください。
DrMcCleod

4

リストしたものはすべて比meta的なツールボックスのツールです。人生のあらゆるものと同様に、さまざまなツールがさまざまなタスクに適しています。

他のエンジニアリング分野と比較して、ソフトウェアは、それ自体で非常に単純な多数の個々の部分で動作します。割り当てステートメントは、部屋の温度変動に応じて異なる評価を行いません。if声明は、所定の場所に腐食し、しばらく後に同じことを返す保持しません。しかし、個々の要素は非常に単純であり、ソフトウェアは人間によって作成されているため、結果が非​​常に大きく複雑になり、人々が精神的に管理できる限界に達するまで、それらの要素はますます大きなピースに結合されます。

ソフトウェアプロジェクトが成長するにつれて、人々はそれらを研究し、その複雑さを管理しようとするツールを作成しました。OOPはその一例です。より多くの抽象的なプログラミング言語が別の手段です。ソフトウェアのお金の多くがより多くのことをしているので、それを達成するためのツールはあなたが見たり読んだりするものです。しかし、これらの状況はあなたには当てはまらないようです。

だから、あなたはそのいずれかを行う必要があると感じないでください。結局のところ、コードは目的を達成するための手段にすぎません。残念ながら、ツールボックスがあなたの心であるときに何が欠けているのかを知るのははるかに難しいので、何が適切で、何が適切でないかについて正しい視点を与えるのは、いくつかのより大きなプロジェクトに取り組むことです。

いずれにせよ、スクリプトが小さい限り、OOPやその他の手法を使用しなくてもかまいません。あなたが説明した問題の多くは、一般的な専門的な組織のスキルです。つまり、古いファイルを失わないことは、すべてのフィールドが対処しなければならないことです。


4

これまでに提供されたすべての優れた提案に加えて、時間をかけて学んだ重要なプラクティスの1つは、コードに詳細なコメントを非常に自由に追加することです。長い時間を経て何かに戻ったとき、それは私にとって最も重要なことです。自分の考えを説明してください。それには少し時間がかかりますが、比較的簡単で、ほとんど痛みはありません。

特に概念や技術が新しく、自分自身に説明するのが難しい場合は、コードのコメントの2〜3倍のコメント行があることがあります。

バージョン管理を行ったり、プラクティスを改善したりしてください。しかし、あなたが行くように自分自身に物事を説明します。本当にうまくいきます。


4

この種のプログラムにはどのような品質が重要ですか?

維持するのが簡単なのか、進化するのが簡単なのかはおそらく問題ではありません。

どれほど効率的かはおそらく関係ありません。

優れたユーザーインターフェイスを備えているかどうか、または悪意のある攻撃者に対して安全であるかどうかはおそらく問題ではありません。

読みやすいことは重要かもしれません。あなたのコードを読んでいる人が、それがすることを主張していることをしていることを簡単に確信させることができます。

それが正しいことは確かに重要です。プログラムの結果が正しくない場合、それは窓の外の科学的結論です。しかし、実際に処理するように要求している入力を正しく処理する必要があるだけです。すべてのデータ値が正の場合、負の入力データ値が与えられた場合に転倒するかどうかはそれほど重要ではありません。

また、ある程度の変更管理を維持することも重要です。科学結果は再現可能である必要があります。つまり、公開する結果を生成したプログラムのバージョンを知る必要があります。開発者が1人しかないため、変更管理はそれほど複雑である必要はありませんが、ある時点に戻って結果を再現できることを確認する必要があります。

したがって、プログラミングのパラダイム、オブジェクト指向、アルゴリズムの優雅さについて心配する必要はありません。明快さと読みやすさ、および経時的な変更の追跡可能性について心配する必要があります。ユーザーインターフェイスについて心配する必要はありません。入力パラメーターの可能な組み合わせをすべてテストすることについて心配する必要はありませんが、結果と結論が有効であることを確信する(そして他のユーザーを確信させる)ために十分なテストを行ってください。


4

多くの(数学/科学)コードを書く学者と同じような環境で働いてきましたが、あなたが説明したのと同じ理由で進歩が遅いです。しかし、うまくいく特定の事柄の1つに気づきました。それはあなたにも役立つと思います。複数のプロジェクトで使用できる特別なライブラリのコレクションを構築して維持することです。これらのライブラリはユーティリティ機能を提供する必要があるため、現在のプロジェクトを問題ドメインに固有に保つのに役立ちます。

たとえば、フィールド内の多くの座標変換(ECEF、NED、lat / lon、WGS84など)を処理するconvert_ecef_to_ned()必要がある場合があるため、次のような関数をという新しいプロジェクトに追加する必要がありCoordinateTransformationsます。プロジェクトをバージョン管理し、部門のサーバーでホストして、他の人が使用できるようにします(できれば改善します)。その後、数年後には、特定の問題/研究ドメインに固有のコードのみを含むプロジェクトを含む堅牢なライブラリのコレクションが必要になります。

より一般的なアドバイス:

  • 特定の問題は、それが何であれ、可能な限り正確にモデル化することを常に目指してください。そうすることで、変数をどのように/どこで/どのように配置するかなどのソフトウェア設計の質問に答えるのがより明確になるはずです。
  • 科学的コードはアイデアと概念を記述し、より創造的で流動的であるため、テスト駆動開発は気にしません。定義するAPI、保守するサービス、機能を変更するときの他の人のコードに対するリスクなどはありません。

他の人に改善させないでください。おそらく、彼らはコードの目的を理解しておらず、単に混乱させるだけです。
mathreadler

@mathreadler一般的なユーティリティライブラリの場合、他の人が混乱するのは少し難しいでしょう、それがアイデアです。
ジググリパフ

汎用ライブラリを台無しにするのが難しいのはなぜですか?あなたが何をしているのかわからない場合や、本当に懸命に努力している場合でも、それはそれほど難しくありません。
mathreadler

@mathreadler通常、座標変換や単位変換などを行う方法は一般に1つしかないためです。
ジググリパフ

通常、メモリへの数値の格納方法、使用する表現、およびライブラリをコンパイルするCPUに応じて、多くの方法があります。あるコーダーは、すべての人が常にIEEEの倍精度を使用すると仮定しますが、別のコーダーはほぼ常に単精度または3分の1の奇妙な形式を使用します。あるコーダーはテンプレートポリモーフィズムを使用しますが、別のコーダーはそれに対してアレルギーを引き起こす可能性があります。
mathreadler

3

以下は私の意見であり、私自身の特定の道に非常に影響を受けています。

コーディングは、物事をどのように行うべきかについて独断的な視点を生み出すことがよくあります。テクニックとツールの代わりに、適切な戦略を決定するために累積値とコストを調べる必要があると思います。

適切で読みやすく、デバッグ可能なソリッドコードを書くには、多くの時間と労力がかかります。多くの場合、計画期間が限られているため、これを行う価値はありません(分析麻痺)。

ある同僚には経験則がありました。本質的に同じようなことを3回目にしている場合は、労力を投資します。そうでなければ、迅速で汚い仕事が適切です。

ある種のテストは不可欠ですが、1回限りのプロジェクトでは、単に目を光らせるだけで十分な場合があります。実質的には、テストとテストインフラストラクチャが不可欠です。値は、コーディング時に解放されることです。コストは、テストが特定の実装に焦点を合わせている場合、テストにもメンテナンスが必要になることです。テストはまた、物事がどのように動作するかを思い出させます。

私自身の1回限りのスクリプト(多くの場合、確率の推定値の検証など)には、2つの小さなことが非常に役立つことがわかりました。1.コードの使用方法を示すコメントを含めます。2.コードを記述した理由の簡単な説明を含めます。これらのことは、コードを記述すると非常に明白になりますが、自明性は時間を浪費します:-)。

OOPは、コードの再利用、抽象化、カプセル化、ファクタリングなどに関するものです。非常に便利ですが、品質の高いコードとデザインを作成することが最終目標でない場合は、簡単に迷子になります。高品質のものを作成するには時間と労力がかかります。


3

ユニットテストにはメリットがあると思いますが、それらは科学的発展にとって疑わしい価値があります-多くの場合、それらは多くの価値を提供するには小さすぎます。

しかし、科学コードの統合テストは本当に好きです。

ETLパイプラインなど、それ自体で動作する可能性のあるコードの小さなチャンクを分離します。次に、データを提供するテストを作成し、etlパイプライン(または単にステップ)を実行してから、結果が期待どおりであることをテストします。テストされたチャンクは大量のコードになる可能性がありますが、テストは依然として価値を提供します。

  1. コードを再実行するための便利なフックがあり、コードを頻繁に実行するのに役立ちます。
  2. テストでいくつかの仮定をテストできます
  3. 何らかの問題が発生した場合、失敗したテストを追加して修正するのは簡単です
  4. 入力データ形式を推測しようとすることに起因する通常の頭痛を避けて、予想される入力/出力を体系化します。
  5. 単体テストほど効率的ではありませんが、ITテストはコードを分割し、コードに境界を追加することを強制するのに役立ちます。

私はこの手法を頻繁に使用しており、多くの場合、比較的読みやすいメイン関数になりますが、サブ関数は非常に長くていことが多いですが、堅牢なI / O境界のためにすぐに変更および再配置できます。


2

私は通常、非常に大規模なソースベースで作業しています。あなたが言及したすべてのツールを使用します。最近、私はサイドプロジェクト用のいくつかのpythonスクリプトの作業を開始しました。最大で数十行から数百行です。習慣から、スクリプトをソース管理にコミットしました。うまくいかないかもしれない実験を試すためにブランチを作成できるので、これは役に立ちました。コードを複製して別の目的で変更する必要がある場合は分岐できます。これにより、元に戻す必要がある場合に備えて、元の状態のままになります。

「単体テスト」の場合、手動で確認する既知の出力を生成するための入力ファイルがいくつかあります。おそらく自動化できますが、それを行うことで節約するよりも、それを行うのに時間がかかるように感じます。おそらく、スクリプトを変更して実行する頻度に依存します。いずれにせよ、それが機能する場合、それを行います。それが価値があるよりも厄介な場合は、時間を無駄にしないでください。


2

コードを書く場合-一般に書く場合と同様に-主な質問は次のとおりです。

どの読者を念頭に置いていますか?または誰があなたのコードを消費しますか?

正式なコーディングガイドラインのようなものは、あなたが唯一の対象者である場合には意味がありません。

一方、そうは言っても、ある方法でコードを書くと役に立ちます。あなたの将来はすぐにそれを理解できます。

したがって、「良いスタイル」は、あなたに最も役立ちます。そのスタイルがどのように見えるかは、私が答えることができない答えです。

150 LOCのファイルに対してOOPまたはユニットテストは必要ないと思います。専用のVCSは、進化するコードがある場合に興味深いでしょう。それ以外の場合.bakは、トリックを行います。これらのツールは病気の治療法であり、あなたも持っていないかもしれません。

おそらく、酔っている間にコードを読んだとしても、コードを読んで理解し、修正できるような方法でコードを書く必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.