一度だけ呼び出される1行関数


120

1行のコードを実行し、プログラムで1回だけ呼び出されるパラメーターレス(編集:必ずしもではない)関数を考えてください(将来再び必要になることは不可能ではありませんが)。

クエリを実行し、いくつかの値をチェックし、正規表現に関係する何かを実行できます。

この背後にある理論的根拠は、読みにくい評価を避けることです。

if (getCondition()) {
    // do stuff
}

where getCondition()は1行関数です。

私の質問は単純です:これは良い習慣ですか?私には大丈夫のようですが、長期については知りません...


9
あなたのコードは、getConditionは()おそらく...グローバルな状態に依存しているとして、これは確かに、悪い習慣になり、暗黙の受信機(例えばこれ)とOOP言語からでない限り
インゴ

2
彼は、getCondition()に引数がある可能性があることを暗示するつもりでしたか?
user606723

24
@Ingo-いくつかのものは本当にグローバルな状態を持っています。現在の時刻、ホスト名、ポート番号などはすべて有効な「グローバル」です。設計エラーは、本質的にグローバルではないものからグローバルを作成しています。
ジェームズアンダーソン

1
どうしてインラインにしないのgetCondition?あなたが言うほど小さくてあまり使用されていない場合、名前を付けても何も達成されません。
-davidk01

12
davidk01:コードの可読性。
wjl

回答:


240

その1行に依存します。行が読み取り可能で簡潔である場合、関数は必要ない場合があります。簡単な例:

void printNewLine() {
  System.out.println();
}

OTOH、複雑で読みにくい式などを含むコード行に関数が適切な名前を付けた場合、それは完全に正当化されます(私にとって)。工夫された例(ここでは読みやすくするために複数の行に分けています):

boolean isTaxPayerEligibleForTaxRefund() {
  return taxPayer.isFemale() 
        && (taxPayer.getNumberOfChildren() > 2 
        || (taxPayer.getAge() > 50 && taxPayer.getEmployer().isNonProfit()));
}

99
+1。ここでの魔法の言葉は「自己文書化コード」です。
コナミマン

8
ボブおじさんが「カプセル化条件」と呼ぶものの良い例。
アンソニーペグラム

12
@Aditya:taxPayerこのシナリオでグローバルであるとは何も言えません。おそらくこのクラスはTaxReturnでありtaxPayer、属性です。
アダムロビンソン

2
さらに、たとえばjavadocで取得して一般に公開できる方法で関数を文書化できます。

2
Bob MartinのClean Codeの@dallinには、半現実的なコードの例がたくさんあります。単一のクラスに多すぎる関数がある場合、おそらくクラスが大きすぎますか?
ペテルトレック

67

はい、これはベストプラクティスを満たすために使用できます。たとえば、1行の長さであっても、より大きな関数内でそのコード行を使用し、その機能を説明する1行のコメントを必要とするよりも、明確に名前が付けられた関数に何らかの作業をさせる方が良いです。また、隣接するコード行は、同じ抽象化レベルでタスクを実行する必要があります。反例は次のようになります

startIgnition();
petrolFlag |= 0x006A;
engageChoke();

この場合、中央の行を適切な名前の関数に移動することをお勧めします。


15
その0x006Aは素敵です。DAは添加物a、b、cを含む炭化燃料の定数としてよく知られています。
コーダー

2
+1自己文書化コードがすべてです:)ところで、ブロックを通して一定の抽象化レベルを維持することに同意すると思いますが、その理由を説明できませんでした。それを拡大してもらえますか?
-vemv

3
petrolFlag |= 0x006A;何らかの意思決定をせずにそれを言っているだけなら、追加機能petrolFlag |= A_B_C; なしで単に言う方が良いでしょう。おそらく、特定の基準を満たしているengageChoke()場合にのみ呼び出すpetrolFlag必要があり、「ここに関数が必要です」と明確に言う必要があります。ほんの少しの雑用で、この答えは基本的にそうではありません:)
ティムポスト

9
メソッド内のコードは同じ抽象化レベルにある必要があることを示すために+1。@vemv、コードを読んでいる間、あなたの心が抽象化の異なるレベルの間を上下にジャンプする必要を回避することによって、コードを理解しやすくするので、それは良い習慣です。抽象化レベルの切り替えをメソッド呼び出し/戻り値(構造的な「ジャンプ」)に結びつけることは、コードをより流andかつクリーンにする良い方法です。
ペテルトレック

21
いいえ、完全に構成された値です。しかし、あなたがそれについて疑問に思っているという事実は、ポイントを強調しています:コードが言っていたならmixLeadedGasoline()、あなたはする必要はないでしょう!
キリアンフォス

37

多くの場合、そのような関数は良いスタイルだと思いますが、他の場所のどこかでこの条件を使用する必要がない場合、ローカルのブール変数を代替と考えることができます。

bool someConditionSatisfied = [complex expression];

これにより、コードリーダーにヒントが提供され、新しい関数の導入が不要になります。


1
boolは、条件関数名が難しいか、誤解を招く可能性がある場合(例:)に特に有益ですIsEngineReadyUnlessItIsOffOrBusyOrOutOfService
-dbkk

2
私はこのアドバイスに悪い考えを経験しました。ブール条件は、機能のコアビジネスから注意をそらします。また、ローカル変数を好むスタイルでは、リファクタリングが難しくなります。
オオカミ

1
@Wolf OTOH、コードの「抽象化の深さ」を減らすために、これを関数呼び出しよりも好みます。IMO、関数へのジャンプは、特にブール値を返すだけの場合、明示的で直接的なコード行よりも大きなコンテキストスイッチです。
カチェ

@Kacheオブジェクト指向をコーディングするかどうかに依存すると思います。OOPの場合、メンバー関数を使用すると、設計の柔軟性がはるかに高くなります。それは本当に文脈に依存します
ウルフ

23

Peterの答えに加えて、その条件を将来のある時点で更新する必要がある場合、単一の編集ポイントのみを持つように提案する方法でカプセル化することにより、

ピーターの例に従って、これが

boolean isTaxPayerEligibleForTaxRefund() {
  return taxPayer.isFemale() 
        && (taxPayer.getNumberOfChildren() > 2 
        || (taxPayer.getAge() > 50 && taxPayer.getEmployer().isNonProfit()));
}

これになります

boolean isTaxPayerEligibleForTaxRefund() {
  return taxPayer.isMutant() 
        && (taxPayer.getNumberOfThumbs() > 2 
        || (taxPayer.getAge() > 123 && taxPayer.getEmployer().isXMan()));
}

単一の編集を行うだけで、全体的に更新されます。保守性に関しては、これはプラスです。

パフォーマンスに関しては、ほとんどの最適化コンパイラは関数呼び出しを削除し、とにかく小さなコードブロックをインライン化します。このようなものを最適化すると、実際にブロックサイズが縮小される可能性があり(関数呼び出し、リターンなどに必要な命令を削除することにより)、通常はインライン化を妨げる可能性がある条件でも安全に実行できます。


2
単一の編集ポイントを保持することの利点は既に認識しています。だからこそ、質問は「...一度呼ばれた」と言うのです。それにもかかわらず、これらのコンパイラーの最適化について知ることは素晴らしいことですが、私はいつも彼らが文字通りこの種の指示に従うと思っていました。
vemv

メソッドを分割して単純な条件をテストすることは、メソッドを変更することで条件が変更される可能性があることを意味する傾向があります。そのような含意は、それが真実である場合に役立ちますが、そうでない場合は危険です。たとえば、コードで物を軽量オブジェクト、重い緑色のオブジェクト、重い緑色でないオブジェクトに分割する必要があるとします。オブジェクトには、重いオブジェクトには信頼性の高いクイック「seemsGreen」プロパティがありますが、軽量オブジェクトでは誤検知を返す場合があります。また、低速な「measureSpectralResponse」関数がありますが、すべてのオブジェクトで確実に機能します。
supercat

seemsGreen軽量オブジェクトでは信頼性が低いという事実は、軽量オブジェクトが緑色であるかどうかをコードがまったく気にしない場合は関係ありません。ただし、「ライトウェイト」の定義が変更され、trueを返す一部の緑以外のオブジェクトがseemsGreen「ライトウェイト」として報告されない場合、「ライトウェイト」の定義を変更すると、オブジェクトをテストするコードが破損する可能性があります「緑」であること。場合によっては、コード内で緑と重みを一緒にテストすると、テスト間の関係が別個のメソッドである場合よりも明確になる場合があります。
supercat

5

読みやすさに加えて(またはそれを補完して)、適切な抽象化レベルで関数を記述することができます。


1
私はあなたが何を意味するのか分からないのではないかと
思う-vemv

1
@vemv:彼は、「適切な抽象化レベル」によって、異なる抽象化レベルが混在するコードにならないことを意味すると思います(つまり、Kilian Fothがすでに言ったこと)。周囲のコードが現在の状況のより高レベルのビューを検討している場合(たとえば、エンジンの実行)、コードが一見取るに足りない詳細(たとえば、燃料のすべての結合の形成)を処理しないようにします。前者は後者を混乱させ、いつでも一度にすべての抽象化レベルを考慮する必要があるため、コードの読み取りと保守が容易ではなくなります。
エゴン

4

場合によります。式が1行だけであっても、関数/メソッドに式をカプセル化した方が良い場合があります。読むのが複雑な場合、または複数の場所で必要な場合は、それをお勧めします。長期的には、単一の変更点と読みやすさを導入したため、メンテナンスが容易です。

ただし、必要のない場合もあります。式がとにかく読みやすい、および/または一箇所に表示される場合は、それをラップしないでください。


3

あなたがそれらのいくつかを持っているなら、それは大丈夫ですが、あなたのコードにそれらがたくさんあるときに問題が発生すると思います。そして、コンパイラーが実行されるか、インターピター(使用する言語に応じて)が実行されると、メモリー内のその関数になります。コンピューターが気付かないとは思わないが、そのうちの3つを持っていると言うことができますが、それらの小さなものが100を数え始めると、システムは一度だけ呼び出されてから破棄されない関数をメモリに登録する必要があります。


スティーブンの答えによると、これは必ずしも起こるとは限りません(コンパイラの魔法を盲目的に信頼することは、とにかく良いことではありませんが)
-vemv

1
ええ、多くの事実に応じてクリアされます。インターセプトされた言語の場合、キャッシュ用の何かをまだインストールしない限り、システムは毎回単一行機能に該当します。コンパイラに関しては、コンパイラがあなたの友人になり、あなたの小さな混乱を解消するか、本当に必要だと思うかどうかは、弱者と平穏な状況の日に重要になります。正確な回数を知っている場合、ループは常に何度か実行され、それを何度もコピーして貼り付けてからループすることができます。
WojonsTech

惑星の位置合わせのために+1 :)が、あなたの最後の文章は私には全く馬鹿げているように聞こえますが、あなたは本当にそれをしますか?
-vemv

それは本当に依存しますほとんどの場合、私はそれが速度の増加やそのような他のことを行うかどうかを確認するために正しい支払い額を取得していない限りそうではありません。しかし、古いコンパイラでは、それをコピーして貼り付けてから、コンパイラを残して9/10を把握する方が適切でした。
-WojonsTech

3

コメントを除いてコードの実際の意味を明確にするために、リファクタリングしているアプリケーションでこの正確なことを最近行いました。

protected void PaymentButton_Click(object sender, EventArgs e)
    Func<bool> HaveError = () => lblCreditCardError.Text == string.Empty && lblDisclaimer.Text == string.Empty;

    CheckInputs();

    if(HaveError())
        return;

    ...
}

好奇心が強い、それはどんな言語ですか?
vemv

5
@vemv:私にはC#のように見えます。
スコットミッチェル

また、コメントよりも追加の識別子を好みます。しかし、これはローカル変数導入しif短くすることとは本当に違いますか?このローカル(ラムダ)アプローチにより、PaymentButton_Click関数全体が読みにくくなります。lblCreditCardErrorあなたの例では、メンバーのようですので、また、HaveErrorオブジェクトに対して有効である(プライベート)述語です。私はこれを支持する傾向がありますが、私はC#プログラマーではないので、抵抗します。
ウルフ

@ウルフねえ、ええ。私はかなり前にこれを書いた:)私は間違いなく今ではかなり違うことをする。実際には、エラーがあったかどうかを確認するために、ラベルの内容を見ていると、なぜ私はちょうどからブール値を返しませんでした...私はうんざりますCheckInputs()???
ジョスフェリー

0

その1行を適切な名前のメソッドに移動すると、コードが読みやすくなります。他の多くの人がすでにそれについて言及しています(「自己文書化コード」)。メソッドに移動することのもう1つの利点は、単体テストが容易になることです。独自のメソッドに分離され、単体テストが行​​われた場合、バグが見つかった場合、バグがこのメソッドに含まれないことを確認できます。


0

すでに多くの良い答えがありますが、言及する価値のある特別なケースがあります。

1行のステートメントにコメントが必要で、その目的を明確に識別できる(つまり、名前を付けることができる)場合は、コメントをAPIドキュメントに拡張しながら関数を抽出することを検討してください。このようにして、関数呼び出しをより速く、より簡単に理解できるようにします。

興味深いことに、現在何もしない場合でも同じことができますが、必要な拡張を思い出させるコメント(非常に近い将来1)

def sophisticatedHello():
    # todo set up
    say("hello")
    # todo tear down

これに変更することもできます

def sophisticatedHello():
    setUp()
    say("hello")
    tearDown()

1)あなたはこれについて本当に確信しているべきです(YAGNIの原則を参照)


0

言語でサポートされている場合、通常はラベル付き匿名関数を使用してこれを実現します。

someCondition = lambda p: True if [complicated expression involving p] else False
#I explicitly write the function with a ternary to make it clear this is a a predicate
if (someCondition(p)):
    #do stuff...

私見これは、複雑な式がif条件を乱雑にしない一方で、小さなスローアウェイラベルでグローバル/パッケージの名前空間を乱雑にしないことの読みやすさの利点を与えるので、良い妥協です。関数 "definition"が使用されている場所で適切であり、定義の変更と読み取りが容易になるという利点もあります。

述語関数である必要はありません。私はこのような小さな関数にも繰り返しボイラープレートを入れるのが好きです(ブラケット構文を乱雑にすることなくpythonリストの生成に特にうまく機能します)。たとえば、PythonでPILを使用する場合の次の簡単な例

#goal - I have a list of PIL Image objects and I want them all as grayscale (uint8) numpy arrays
im_2_arr = lambda im: array(im.convert('L')) 
arr_list = [im_2_arr(image) for image in image_list]

なぜ「ラムダp:[pを含む複雑な表現] else false "」が「ラムダp:[pを含む複雑な表現]」の代わりに真か?8
ハンス・ピーター・シュトルー

@hstoerrは、その行のすぐ下のコメントにあります。someConditionが述語であることを明示的に知らせたいです。厳密には不要ですが、あまりコーディングしていない人が読む科学的なスクリプトをたくさん書いていますが、個人的には[] == False他の人がそれを知らないので混乱させるのではなく、余分な簡潔さを持つ方が適切だと思います「常に」直感的ではない類似のpythonic等価性。基本的に、someConditionが実際には述語であることを示す方法です。
古典的な

ちょうど私の明白な間違いをクリアする[] != Falseが、[]ブール値にキャストするときのように偽である
crasic

@crasic:[]がFalseに評価されることを読者が知ることを期待しないときは、[]がFalseに評価されることを読者に知らせる必要があるのlen([complicated expression producing a list]) == 0ではなく、を使用True if [blah] else Falseすることをお勧めします。
ライライアン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.