プログラムで非常に多くのルールとマジックナンバーを管理するにはどうすればよいですか?


21

私はプログラミングに多少慣れており(私は貿易で機械エンジニアです)、ダウンタイム中に工場周辺のさまざまな人々からの入力に基づいて(solidworks)パーツを生成する小さなプログラムを開発しています。

わずかな入力(正確には6つ)に基づいて、それぞれ最大12個のパラメーターを取ることができる何百ものAPI呼び出しを行う必要があります。すべては、パーツを処理するすべての人にインタビューした後に収集した一連のルールによって生成されます。私のコードのルールとパラメーターのセクションは250行で、成長しています。

だから、コードを読みやすく管理しやすくする最良の方法は何ですか?すべてのマジックナンバー、すべてのルール、アルゴリズム、およびコードの手続き部分をどのように区分するのですか?非常に詳細で詳細なAPIに対処するにはどうすればよいですか?

私の主な目標は、誰かに私の情報源を渡して、私の入力なしで、私がやっていることを彼らに理解させることです。


7
これらのAPI呼び出しの例をいくつか提供できますか?
ロバートハーヴェイ14


「コンピュータサイエンスのすべての問題は、別のレベルの間接参照によって解決できます」-David Wheeler
Phil Frost

...間接参照のレベルが多すぎる場合を除く:)
ダンライオンズ14

1
コードを見ずに質問に答えることは困難です。codereview.stackexchange.comにコードを投稿して、他のプログラマーからアドバイスを受けることができます。
ギルバートルブラン14

回答:


26

説明に基づいて、おそらくデータベースの素晴らしい世界を探検したいと思うでしょう。あなたが説明するマジックナンバーの多くは、特にそれらが部分に依存している場合、実際にはデータであり、コードではないようです。データがパーツにどのように関連するかを分類し、そのデータベース構造を定義できれば、運がずっと良くなり、長期的にアプリケーションを拡張するのがはるかに簡単になります。

「データベース」は必ずしもMySQLまたはMS-SQLを意味するわけではありません。データの保存方法は、プログラムの使用方法や作成方法などに大きく依存します。SQLタイプのデータベースを意味する場合もあれば、単にフォーマットされたテキストファイルを意味する場合もあります。


7
彼はより大きな問題を抱えているように見えますが、データベース内のデータを体系化することに同意しました。
ロバートハーヴェイ14

完全に異なる部分を作成するプログラムを作成していた場合、そう、これが道です。ただし、4つのわずかに異なる構成を持つ1つのパーツにすぎません。巨大なものになることはありません(開発者を雇って開発者を雇わない限り、そのようなことは重要ではありません)。とはいえ、私が終了してリファクタリングしたいと思った後は、素晴らしい学習体験になると思います。
user2785724 14

1
ソフトコーディングのように聞こえます。データベースは可変状態用です。定義上、マジックナンバーは変更できません。
フィルフロスト14

1
@PhilFrost:それらを不変にすることができます。最初にテーブルを作成した後、それらに書き込みをしないでください。
ロバートハーヴェイ

1
@PhilFrost:さて、彼が扱っているAPIを見きました。その大きさだけが注目に値します。彼が必要としない限り、彼はデータベースをまったく必要としないかもしれません。
ロバートハーヴェイ

14

これを複数の部分に拡張することを予期しない限り、私はまだデータベースを追加したがりません。データベースを持っているということは、あなたのために学ぶべきものが山ほどあることを意味し、他の人のためにそれを機能させるためにインストールするものがたくさんあります。埋め込みデータベースを追加すると、最終的な実行可能ファイルは移植可能になりますが、ソースコードを持っている人はもう1つ作業をする必要があります。

明確な名前の定数とルール実装関数のリストが大いに役立つと思います。すべてに自然な名前を付け、リテラシープログラミングに焦点を当てる場合 テクニックに、読みやすいプログラムを作成できるはずです。

理想的には、次のようなコードになります。

LeftBearingHoleDepth = BearingWidth + HoleDepthTolerance;
if (not CheckPartWidth(LeftBearingHoleDepth, {other parameters})
    {whatever you need to adjust}

定数がどの程度ローカルであるかに応じて、可能な場合に使用される関数で定数を宣言したいと思うでしょう。回すと非常に便利です:

SomeAPICall(10,324.5, 1, 0.02, 6857);

const NumberOfOilDrainHoles = 10
const OilDrainHoleSpacing = 324.5
{etc}
SomeAPICall(NumberOfOilDrainHoles, OilDrainHoleSpacing, {etc}

これにより、主に自己文書化コードが提供され、コードを変更する人が追加するものに同様に意味のある名前を付けるように奨励されます。ローカルで起動すると、蓄積する定数の総数を簡単に処理できます。値が必要なものであることを確認するために、定数の長いリストをスクロールし続ける必要がある場合、少し面倒です。

名前のヒント:最も重要な単語を左側に配置します。あまり読めないかもしれませんが、物事を見つけやすくします。ほとんどの場合、油だめを見て、ボルトについて疑問に思っており、ボルトを見て、どこにあるのか疑問に思っていないので、BoltThreadPitchSumpではなくSumpBoltThreadPitchと呼びます。次に、定数のリストをソートします。後で、すべてのスレッドピッチを抽出するには、テキストエディターでリストを取得し、検索機能を使用するか、grepなどのツールを使用して「ThreadPitch」を含む行のみを返します。


1
Fluentインターフェイスの作成も検討してください
イアン14

これが私のコードの実際の行です。変数名の意味がわかっていれば、ここで何が起こっているのか意味がありますか(引数はx1、y1、z1、x2、y2、z2が二重になっています)? .CreateLine(m_trunion_support_spacing / 2, -((m_flask_length / 2) + m_sand_ledge_width + m_wall_thickness), -m_flange_thickness, m_trunion_support_spacing / 2, -((m_flask_length / 2) + m_sand_ledge_width + m_wall_thickness), -m_flask_height + m_flange_thickness)
user2785724 14

エディター統合でctagsを使用して定数を見つけることもできます。
フィルフロスト14

3
@ user2785724それは混乱です。何してるの?特定の長さと深さの溝を作っていますか?次に、という関数を作成できますcreateGroove(length, depth)。機械エンジニアに説明するように、達成したいことを説明する機能を実装する必要があります。それがリテラシープログラミングの目的です。
フィルフロスト14

これは、3D空間に1本の線を描くAPI呼び出しです。6つの引数はそれぞれ、プログラムの異なる行にあります。API全体が狂っています。どこで混乱を起こすかわからなかったので、そこで作りました。API呼び出しとその引数がわかっていれば、使い慣れたパラメーターを使用してエンドポイントが何であるかを確認し、それをパーツに関連付けることができます。SolidWorksに精通したい場合、APIは絶対に迷宮です。
user2785724 14

4

あなたの質問は、計算をどのように構造化するのですか?コードである「ルールセット」とデータである「マジックナンバーセット」を管理することに注意してください。(それらは「コードに埋め込まれたデータ」として見ることができますが、それでもデータです)。

さらに、あなたのコードは、「他の人に理解しやすい」作ること(例:「を参照してください。実際には、すべてのプログラミングパラダイムの一般的な目標である実装パターンをケントベック、または」で「クリーンコード同じ目標を述べるソフトウェアの作者のためにロバート・C. Martinによります」あなたのように、どんなプログラムでも)。

これらの本のヒントはすべてあなたの質問に当てはまります。「マジックナンバー」と「ルールセット」専用のヒントをいくつか抽出します。

  1. 名前付き定数と列挙を使用して、マジックナンバーを置き換えます

    定数の例

    if (partWidth > 0.625) {
        // doSomeApiCall ...
    }
    return (partWidth - 0.625)
    

    名前付き定数に置き換える必要があります。これにより、後の変更でタイプミスが発生してコードが破損しないようにすることができます0.625

    const double MAX_PART_WIDTH = 0.625;
    
    if (partWidth > MAX_PART_WIDTH) {
        // doSomeApiCall ...
    }
    return (partWidth - MAX_PART_WIDTH)
    

    列挙の例

    列挙は、一緒に属するデータをまとめるのに役立ちます。Javaを使用している場合、Enumはオブジェクトであることを忘れないでください。それらの要素はデータを保持でき、すべての要素を返すメソッドを定義したり、プロパティをチェックしたりできます。ここで、Enumは別のEnumの構築に使用されます。

    public enum EnginePart {
        CYLINDER (100, Materials.STEEL),
        FLYWHEEL (120, Materials.STEEL),
        CRANKSHAFT (200, Materials.CARBON);
    
        private final double maxTemperature;
        private final Materials composition;
        private EnginePart(double maxTemperature, Materials composition) {
            this.maxTemperature = maxTemperature;
            this.composition = composition;
        }
    }
    
    public enum Materials {
        STEEL,
        CARBON
    }
    

    利点は次のとおりです。スチールまたはカーボンで作られていない EnginePartを誤って定義することはできません。また、コンテンツでチェックされる文字列である場合のように、「asdfasdf」というEnginePartを導入することはできません

  2. Strategyパターンファクトリメソッドパターンは、 Strategyパターンの場合には、;(Factoryパターンの場合には、使用量が何かを構築して、「ルール」をカプセル化し、それらを利用した別のオブジェクトに渡す方法について説明します使い方は何でもいいです)。

    Factoryメソッドパターンの例

    2つのタイプのエンジンがあると想像してください。各パーツコンプレッサーに接続する必要があるエンジンと、各パーツを他のパーツに自由に接続できるエンジンです。ウィキペディアからの適応

    public class EngineAssemblyLine {
        public EngineAssemblyLine() {
            EnginePart enginePart1 = makeEnginePart();
            EnginePart enginePart2 = makeEnginePart();
            enginePart1.connect(enginePart2);
            this.addEngine(engine1);
            this.addEngine(engine2);
        }
    
        protected Room makeEngine() {
            return new NormalEngine();
        }
    }
    

    そして、別のクラスで:

    public class CompressedEngineAssemblyLine extends EngineAssemblyLine {
        @Override
        protected Room makeRoom() {
            return new CompressedEngine();
        }
    }
    

    興味深いのは、AssemblyLineコンストラクターが、処理するエンジンのタイプから分離されていることです。たぶんaddEngine方法は、リモートAPIを呼び出しています...

    戦略パターンの例

    Strategyパターンは、オブジェクトの動作を変更するためにオブジェクトに関数を導入する方法を説明します。あなたが時々部品を磨きたい、時にはペイントしたい、そしてデフォルトではその品質をレビューしたいと想像してみましょう。これは、Stack Overflowから改造されたPythonの例です

    class PartWithStrategy:
    
        def __init__(self, func=None) :
            if func:
                self.execute = func
    
        def execute(self):
            # ... call API of quality review ...
            print "Part will be reviewed"
    
    
    def polish():
        # ... call API of polishing department ...
        print "Part will be polished"
    
    
    def paint():
        # ... call API of painting department ...
        print "Part will be painted"
    
    if __name__ == "__main__" :
        strat0 = PartWithStrategy()
        strat1 = PartWithStrategy(polish)
        strat2 = PartWithStrategy(paint)
    
        strat0.execute()  # output is "Part will be reviewed"
        strat1.execute()  # output is "Part will be polished"
        strat2.execute()  # output is "Part will be painted"
    

    これを展開して、実行するアクションのリストを保持し、executeメソッドから順番に呼び出すことができます。たぶん、この一般化はBuilderパターンとしてよりよく説明できるかもしれませんが、ちょっと、私たちはうるさくなりたくないですよね?:)


2

ルールエンジンを使用することもできます。この質問で説明されているように、ルールエンジンは、特定の結果に必要な基準を理解可能な方法でモデル化するように設計されたDSL(ドメイン固有言語)を提供します。

ルールエンジンの実装によっては、コードを再コンパイルせずにルールを変更することもできます。また、ルールは独自のシンプルな言語で記述されているため、ユーザーがルールを変更することもできます。

運がよければ、使用しているプログラミング言語にすぐに使用できるルールエンジンがあります。

マイナス面は、プログラミングの初心者なら難しいかもしれないルールエンジンに精通しなければならないことです。


1

この問題に対する私の解決策はまったく異なります。レイヤー、設定、LOPです。

最初にAPIをレイヤーにラップします。一緒に使用されるAPI呼び出しのシーケンスを見つけ、それらを独自のAPI呼び出しに結合します。最終的には、基になるAPIを直接呼び出すのではなく、ラッパーを呼び出すだけにする必要があります。ラッパー呼び出しは、ミニ言語のように見えるはずです。

次に、「設定マネージャー」を実装します。これは、名前と値を動的に関連付ける方法です。このようなもの。別のミニ言語。

Baseplate.name="Base plate"
Baseplate.length=1032.5
Baseplate.width=587.3

最後に、デザインを表現するための独自のミニ言語を実装します(これは言語指向プログラミングです)。この言語は、ルールと設定に貢献するエンジニアとデザイナーが理解できるものでなければなりません。頭に浮かぶこのような製品の最初の例はGnuplotですが、他にもたくさんあります。Pythonは使用できますが、個人的には使用しません。

これは複雑なアプローチであり、あなたの問題にとってはやりすぎかもしれないし、まだ習得していないスキルを必要とするかもしれないことを理解しています。それはちょうど私がそれをする方法です。


0

質問を正しく受け取ったかどうかはわかりませんが、いくつかの構造で物事をグループ化する必要があるようです。C ++を使用している場合、次のようなものを定義できます。

struct SomeParametersClass
{
    int   p1;  // this is for that
    float p2;  // this is a different parameter
    ...
    SomeParametersClass() // constructor, assigns default values
    {
        p1 = 42; // the best value that some guy told me
        p2 = 3.14; // looks like a know value, but isn't
    {
};

struct SomeOtherParametersClass
{
    int   v1;  // this is for ...
    float v2;  // this is for ...
    ...
    SomeOtherParametersClass() // constructor, assigns default values
    {
        v1 = 24; // the best value 
        v2 = 1.23; // also the best value
    }
};

プログラムの開始時にこれらをインスタンス化できます。

int main()
{
    SomeParametersClass params1;
    SomeOtherParametersClass params2;
    ...

API呼び出しは次のようになります(署名を変更できないと仮定):

 SomeAPICall( params1.p1, params1.p2 );

APIの署名を変更できる場合は、構造全体を渡すことができます。

 SomeAPICall( params1 );

すべてのパラメーターをより大きなラッパーにグループ化することもできます。

struct AllTheParameters
{
    SomeParametersClass      SPC;
    SomeOtherParametersClass SOPC;
};

0

誰もこれについて言及していないことに驚いています...

あなたが言った:

私の主な目標は、誰かに私の情報源を渡して、私の入力なしで、私がやっていることを彼らに理解させることです。

それで、他の答えのほとんどは正しい軌道に乗っています。私は間違いなくデータベースがあなたを助けることができると思います。しかし、あなたを助けるもう一つのことは、コメント、適切な変数名、適切な組織化/懸念の分離です。

他のすべての答えは技術的に非常に基づいていますが、ほとんどのプログラマーが学ぶ基本を無視しています。あなたは貿易の機械的熱狂者なので、私の推測では、このスタイルのドキュメンテーションに慣れていないのでしょう。

コメントを作成して適切で簡潔な変数名を選択すると、読みやすくなります。どちらがわかりやすいですか?

var x = y + z;

または:

//Where bandwidth, which was previously defined is (1000 * Info Rate) / FEC Rate / Modulation * carrier spacing / 1000000
float endFrequency = centerFrequency + (1/2 bandwidth);

これは言語に依存しません。使用しているプラ​​ットフォーム、IDE、言語などに関係なく、適切なドキュメントは、誰かがコードを理解できるようにするための最もクリーンで簡単な方法です。

次に、これらのマジックナンバーと多くの懸念事項を管理しますが、GrandmasterBのコメントはそれをうまく処理したと思います。

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