実生活でクリーンなコードがどのように見えるかを把握する際の問題


10

私は現在、Robert C. Martinによる「クリーンコード:アジャイルソフトウェアのクラフトマンシップのハンドブック」を読んで作業しています。著者は、関数が1つのことだけを行うべきであり、したがって比較的短い方法について話します。具体的にはマーティンはこう書いている:

これは、ifステートメント、elseステートメント、whileステートメントなど内のブロックが1行の長さであることを意味します。おそらくその行は関数呼び出しでなければなりません。これにより、囲まれた関数が小さく保たれるだけでなく、ブロック内で呼び出された関数にわかりやすい名前を付けることができるため、記録的な価値が追加されます。

これは、関数が入れ子構造を保持するのに十分な大きさであってはならないことも意味します。したがって、関数のインデントレベルは1または2以下にする必要があります。もちろん、これにより機能が読みやすくなり、理解しやすくなります

これは理にかなっていますが、私がクリーンなコードと見なしているものの例と矛盾しているようです。たとえば、次の方法を考えます。

    public static boolean millerRabinPrimeTest(final int n) {
        final int nMinus1 = n - 1;
        final int s = Integer.numberOfTrailingZeros(nMinus1);
        final int r = nMinus1 >> s;
        //r must be odd, it is not checked here
        int t = 1;
        if (n >= 2047) {
            t = 2;
        }
        if (n >= 1373653) {
            t = 3;
        }
        if (n >= 25326001) {
            t = 4;
        } // works up to 3.2 billion, int range stops at 2.7 so we are safe :-)
        BigInteger br = BigInteger.valueOf(r);
        BigInteger bn = BigInteger.valueOf(n);

        for (int i = 0; i < t; i++) {
            BigInteger a = BigInteger.valueOf(SmallPrimes.PRIMES[i]);
            BigInteger bPow = a.modPow(br, bn);
            int y = bPow.intValue();
            if ((1 != y) && (y != nMinus1)) {
                int j = 1;
                while ((j <= s - 1) && (nMinus1 != y)) {
                    long square = ((long) y) * y;
                    y = (int) (square % n);
                    if (1 == y) {
                        return false;
                    } // definitely composite
                    j++;
                }
                if (nMinus1 != y) {
                    return false;
                } // definitely composite
            }
        }
        return true; // definitely prime
    }
}

このコードは、Apache Commonsのソースコードリポジトリのhttps://github.com/apache/commons-math/blob/master/src/main/java/org/apache/commons/math4/primes/SmallPrimes.java

メソッドは私には非常に読みやすく見えます。このようなアルゴリズムの実装(Miller-Rabin Probabilistic Primality Testの実装)の場合、本に定義されているように、コードをそのまま維持し、それを「クリーン」と見なすことは適切ですか?または、アルゴリズムを本質的に「1つのことだけを行う」関数への一連の呼び出しにするメソッドを抽出することから、これと同じくらい読みやすいものになるでしょうか?メソッド抽出の簡単な例の1つは、最初の3つのifステートメントを次のような関数に移動することです。

private static int getTValue(int n)
    {
        int t = 1;
        if (n >= 2047) {
            t = 2;
        }
        if (n >= 1373653) {
            t = 3;
        }
        if (n >= 25326001) {
            t = 4;    
        }
        return t;
    }

注:この質問は重複の可能性とは異なります(この質問は私にとっても役立ちます)。私はClean Codeの作者の意図を理解しているかどうかを判断しようとしているため、具体的な例を提供して、さらに詳しく説明しています。コンクリート。


3
私が見る限り、この機能は1つのことしか行いません...目に見える副作用はありません。何があなたをきれいにしないと思いますか?この関数のどの部分をきれいにするために別の関数に入れる価値があると思いますか?
2017

14
あなたの質問のタイトルは「実生活」の状況を要求し、あなたの例は私に非実在関数の完全な例のように見えます(少なくとも、アプリケーションまたはWeb開発者の99.9%にとって)。もちろん、それはその特定の分野で働いている数論者、数学者、コンピューター科学者にとっては現実の機能かもしれません。
Doc Brown


2
はい、私にとって計算代数的数論の分野で現在開発している私にとってこれは現実です:)
1west

2
あなたが説明するように私はおそらくgetTFactor()をリファクタリングするでしょう。
user949300 2017

回答:


17

「クリーンコード」はそれ自体が目的ではなく、目的を達成するための手段です。大きな関数を小さな関数にリファクタリングし、他の方法でコードをクリーンアップする主な目的は、コードの進化と保守を維持することです。

"Miller-Rabin"プライムテストのような非常に特殊な数学的アルゴリズムを教科書から選ぶとき、ほとんどのプログラマーはそれを進化させたくありません。彼らの標準的な目標は、それを教科書の疑似コードから彼らの環境のプログラミング言語に正しく転送することです。この目的のために、私はできる限りテキストに忠実に従うことをお勧めします。これは通常、リファクタリングしないことを意味します。

ただし、その分野で数学者として働いてそのアルゴリズムに取り組み、それを変更または改善しようとしている人にとって、IMHOはこの関数をより小さな名前の付いたものに分割するか、または一連の「マジックナンバー」を名前付き定数で置き換えることができます。他の種類のコードと同様に、コードへの変更を容易にするのに役立ちます。


1
これはまさに私が探していたものです。私が開発している分野でクリーンなコードプラクティスをいつ使用するかを決定するのに苦労していました。あなたの答えは私が探していた明確さを提供します。ありがとう!
2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.