web-dev-qa-db-ja.com

式を関数としてラップする方法は、クリーンコードですか?

別のプログラマーがチームで作業を開始し、パッチを提出しました。必要なのは、いくつかの条件を比較してチェックし、結果に基づいてプロパティを設定するものを持つことでした。パッチは、本質的に、次のような要件を実装するクラスです。

_class Processor {
    A a;
    B b;

    public function process(A a, B b) {
        this.a = a;
        this.b = b;

        if (false == this.isVerified() && this.equalNames()) {
            this.setVerified();
        }
    }

    private function equalNames(): bool {
        return this.a.name() == this.b.name();
    }

    private function isVerified(): bool {
        return this.a.isVerified();
    }

    private function setVerified() {
        this.a.setVerified(true);
    }
}
_

実際のコードはもう少し詳細である可能性がありますが、疑似コードは十分詳細であると思います。基本的に、びっくりしたコードをレビューしたときに何が起こるか。さらに悪いことに、彼は自分のコードが「クリーンなコード」であることを超えて、「どれだけきれいに見えるか」と説明することはできませんでした。本を読んでください。実際、誰かが彼のコードを利用するには、まずそのクラスをインスタンス化し、次にprocess()を呼び出す必要があると指摘しました。

仕事を終わらせる必要があるので、私たちの1人はそのクラスをAServiceクラス内のメソッドに置き換えることを提案しました:

_public function verifyA(A a, B b) {
    if (!a.isVerified() && a.name() == b.name()) {
        a.setVerified(true);
    }
}
_

それが最終的にマージされたものです。今、私は彼が無視されたと感じたくないので、関数で単一の論理式をラップすることの背後にある真実があるかどうかを知りたいと思っています(彼のコードレビューでは、彼は常にみんなのifそれらを機能させるためのステートメント、そして今のところ誰も義務付けていない)。また、彼または私たちを作るために、正しい方法が何であれ説明する方法を理解しています。

Clean Codeの本をまだ読んでいません。ボクおじさんのブログを読んだり、ビジネスロジックを関数に組み込んだりすることを理解しています。私の質問は、関数に単一の論理式を置き、もしあれば本を参照することについてより具体的です。 2番目の質問は、この問題が何度か発生し、チームのダイナミクスに影響を与えているため、この問題の議論をどのように処理するかです。

4
imel96

「クリーンなコード」を構成するものは、常にコンテキストに依存します。アプリケーションの要件に応じて、まったく同じコード行を完全にクリーンにするか、非常に複雑にすることができます。

問題の設計では、依存性注入を使用して、クライアントに影響を与えることなくプロセスの実装を置き換えることができます。したがって、これはクリーンであり、SOLIDコードifにはこの要件があります。そのような要件がない場合は、複雑すぎて、YAGNIに違反しています。 KISS原則。

あなたも開発者もデザインの理由を説明できなかったので、2番目のオプションが最も可能性が高いと思います。おそらく、開発者はこのデザインを意味のあるコンテキストで見たことがありますが、意味のないコンテキストで適用しました。

12
JacquesB

それはすべて命名に関するものです。

比較の結果が何を意味するかを明確にする関数名を見つけた場合は、それを関数にラップします。

あなたの例ではこれは当てはまりません。たとえば、isVerified()は、実際には何が検証されているかを確認します。この場合、

return this.a.isVerified();

しかしそれは

return this.b.isVerified();

または

return this.a.isVerified() && this.b.isVerified();
5
Frank Puffer

また、おそらく最初に、クラスを使用するクライアントプログラマーに提供される抽象化を確認し、次に2番目に、クリーンアップなどの実装を確認する必要があります。

クライアントの使用法が次のようになっている場合:

_new Process().process(a,b);
_

Processインスタンスは、将来の参照用にキャプチャされていません。このクラスはやり過ぎであり、有用な抽象化を提供しないと思います(たとえば、a.verify(b);

もう1つの潜在的な使用法は、Processインスタンスをキャプチャして保持し、異なるaとbでそのプロセスメソッドを使用することです。

_Process p = new Process();
...
p.process(a,b);
...
p.process(aa,bb);
...
_

これは素晴らしい抽象的であるとは言えません。しかし、私はもっと悪いことを見てきました。 (何らかの理由でProcessインスタンスがなんらかの構成をとるようになっていれば、それは半分悪いことではありません。)

ただし、実装に関しては、フィールドを_A a;_および_B b;_を削除して、パラメーターをプライベートメソッドに渡し、クラスをこの使用法に適したものにしてください(@MrCocheseが正しく指摘しているため、スレッドセーフです)。 。

さらに、表示する両方のアプローチで、abに対して検証され、次にaは検証済みとしてマークされますが、特定のb

私が検討するアプローチは次のとおりです。これは、実装ではなくクライアントプログラマの使用法から示したものです。抽象化は、内部の実装の詳細に対する最も重要な考慮事項として提供されているためです。

_Pair p = new Pair(a,b);
...
p.verify();
...
_

これで、クラスに値する適切な抽象化ができました。ペアを検証できることは明らかです。検証すると、その意味がわかります。つまり、aがそのbに対して検証されたということです。また、検証状態をPairクラスではなくAクラスにキャプチャします。

4
Erik Eidt

例のためにクラスの名前をProcessorに変更したと思います。しかし、名前は実際にはここで重要だと思います。

開発者が定義されたビジネスコンセプトにマップするクラスの名前を思い付くことができる場合、彼のアプローチは意味があるかもしれませんが、クラスがステートフルであると疑っています(特に、名前がerで終わる場合) )。定義されたビジネスコンセプトにマッピングされる場合、クラスは拡張ポイントとして機能し、保守性を向上させることができます。

一方、馬鹿げた名前である可能性がある場合は、操作を連結した名前(CheckVerifiedAndUpdateProcessorなど)、または新奇な名前(CleanNameProcessorなど)です。前者の場合、複合式が使用されている場所に開発者が新しいクラスを追加するかどうかを尋ねます。後者の場合は、コードを管理しているチームの全員がプライベートな専門用語を学ぶことを期待するかどうか尋ねます。どちらもひどい考えです。

2
John Wu

あなたの質問は本当にワンライナーですか、プログラマーのやり方ですか?あなたはタグ「チームワーク」を設定したので、これはあなたが探しているものであり、経験から引き出したものかもしれません。

あなたが提示したコンテキストは小さすぎて何かを作ることができません。コード全体が 泥の大きなボール に向かっている(またはそうなっている)場合、彼は正しいかもしれません。その場合、彼がアプリケーションを手助けして正しい軌道に乗ろうとしているので、私は彼をサポートします。実際、彼は本当にプロジェクトに参加しており、成功を望んでいると言えるでしょう。そして、彼は常に他人のコードの欠陥を指摘しています。彼は本当に物事を正しい軌道に乗せたいと思っています。

あなたは私たちに少し与えて、このビットが正しいか間違っているかを尋ねました。全体のコンテキスト、アーキテクチャ、ビジネスプロセス、インフラストラクチャ、制限などについて何も知らなくても、私たちは延々と議論することができ、それは主に誰かの好みについてです。

一方、長年にわたって機能し、実績のある強力で優れたアーキテクチャと、ソフトウェアとインフラストラクチャに課せられ、費用対効果の高い拡張機能とさらなるビジネス要件がある場合、チームは無関係であるため、彼は間違っています。彼のコードがきれいかどうか、ワンライナーを含む。しかし、これは主観的でもあります。なぜなら、ほとんどの人は自分のコードを最良のものとして認識しているからです。彼はあなたが見ない何かを見ているかもしれません。たぶん、あなたは彼に討論としてチーム全体に対してこの件について話させるべきでしょうか?

2
civan

関数名の方が、単純な式よりも抽象化されている場合があります。私はしばしば自分自身を使用していることに気づきます:

double square(double in)
{
   return in*in;
}

の使用

double length_squared = square(a) + square(b) + square(c);

の使用よりもよく読む

double length_squared = a*a + b*b + c*c;

squarea、およびbが他の関数呼び出しから取得される場合、cの使用はより意味があります。

double length_squared = square(aFun(x)) + square(bFun(x)) + square(cFun(x));

間違いなくより良いです:

double a = aFun(x);
double b = bFun(x);
double c = cFun(x);
double length_squared = a*a + b*b + c*c;

あなたは絶対に使いたくない:

double length_squared = aFun(x)*aFun(x) + bFun(x)*bFun(x) + cFun(x)*cFun(x);

aFunなどの場合、大きなオーバーヘッドがあります。

2
R Sahu

このような考えの学校があります。それは過度だと思う他の多くの学校があります。個人的には、このような単純な比較の場合、それは完全なBSであり、実際には有害だと思います(特に、比較が頻繁に行われる場合、パフォーマンスに重大な打撃を与えます)。

「ロジック」は、「複数回呼び出される可能性がある場合は、関数にする」のようなものです。もちろん、論理的な比較は、1つのコードで複数回呼び出されます。

私が見た中で最悪だったのは、最初のプログラマが関数を作成したCコードです。

int add(int a, int b) {
  return a+b;
}

その関数は、プログラムの実行中に何百万回も呼び出され、その結果、大量の関数スタックの作成とアンラップが発生したため、クロールが遅くなっていました。

このプログラムは非常に単純で、その結果、24時間ごとに1回実行する必要がある場合、作業を完了するのに36時間かかりました。これらのばかげた機能と一部のループ展開を削除することで、ランタイムは16時間に戻り、その時点で顧客は満足していました。私たちはそれをさらに戻すことができたかもしれませんが、それは不要な費用と考えられました。

1
jwenting