機能が短すぎますか?


125

同じロジックを複数回記述していることに気付くたびに、通常、それを関数に固定するため、アプリケーション内でそのロジックを維持する必要がある場所は1つだけです。副作用は、次のような1行または2行の関数になることです。

function conditionMet(){
   return x == condition;
}

または

function runCallback(callback){
   if($.isFunction(callback))
     callback();
}

これは怠け者ですか、それとも悪い習慣ですか?これは、非常に小さなロジックの関数呼び出しの数が多くなるためです。


3
いいえ、短すぎません。C#では、プロパティとして 'condition met'を実行します。これはエレガントであり、の使用を検討しAssert.AreEqual<int>(expected, actual, message, arg1, arg2, arg3, ...);ます。2番目のものはそのままで問題ありません。例外などをスローするかどうかを指示するオプションのブールフラグを含める可能性があります。コールバックが関数ではない場合。
ジョブ

38
はい、唯一の場合:関数MyFunctionを(){}
スパイ

5
def yes(): return 'yes'
-Dietbuddha

3
@Spooks -私はここに分割毛だが、空の関数は、少なくともアダプタクラスのために有効であるにMouseMotionAdapterを例えばdownload.oracle.com/javase/6/docs/api/java/awt/event/...。もちろん、これは言語の制限を回避する唯一の方法です。
アレクシYrttiaho

3
@dietbuddha:要件が変更されました-来週のデモでは2つの言語をサポートする必要があります。「def yes()」と書いた男は、「def yes(:(IsFrench()? 'Oui': 'Yes')」に変更する前に非常に嬉しかった
Andrew Shepherd

回答:


167

Hehe、Brown氏、私が会うすべての開発者を説得して、機能をこれほど小さく保つことができれば、ソフトウェアの世界はより良い場所になると信じています!

1)コードが読みやすくなります。

2)読みやすさのために、コードのプロセスを把握するのは簡単です。

3)DRY-自分自身を繰り返さないでください-これに非常に順応しています!

4)テスト可能。小さな関数は、あまりにも頻繁に見られる200行のメソッドよりもテストが100万倍簡単です。

ああ、パフォーマンスの点で「関数ホッピング」を心配しないでください。「リリース」ビルドとコンパイラーの最適化がこれを非常にうまく処理してくれます。パフォーマンスは、システム設計の他の場所の99%の時間です。

これは怠け者ですか?-非常に反対です!

これは悪い習慣ですか?- 絶対違う。あまりにも一般的であるtarボールや「God Objects」よりも、この方法でメソッドを作成した方が良いです。

私の仲間の職人の良い仕事を続けてください;)


40
あなたは質問に答えているのではなく、より極端なアプリケーションがここで質問されているルールを説いているだけです。

4
たった1つの賛成票の制限にイライラすることはあまりありません。しかし、この答えについては、+ 1は正義ではないようです。
ベヴァン

4
+5 ...ああ、+ 1しかできない。また、MeshManをサポートするには、次のRobert C. Martin Clean codeの本をお勧めします。この本は、私が今プログラムする方法を本当に変えています。
エリックカール

10
非常に好感の持てる答えですが、私はその大部分に同意しません:1)非常に小さな関数は、各関数に必要なすべての配管のために、全体としてより多くのコードをもたらします。これは、特に本当に大きなプロジェクトの場合、可読性を低下させる可能性があります。また、言語に依存します。2)ポイント1に完全に依存するため、質問とは関係ありません。3)何?runCallbackは「コールバック」を4回繰り返し、3回は同じ変数を参照して単一の関数を実行します。4)200行以上のメソッドはもちろんテストできませんが、そこから1行に至るまでには長い道のりがあります。
l0b0

10
-1:ここで最も恐ろしいことは、この回答に与えられた賛成票の数です。
ルカ

64

次のいずれかの場合、リファクタリングされたメソッドは短すぎると思います。

  • メソッドにする以外の目的で、プリミティブ操作を複製します。

例:

boolean isNotValue() {
   return !isValue();
}

または...

  • コードは一度だけ使用され、その意図は一目で理解しやすいです。

例:

void showDialog() {
    Dialog singleUseDialog = new ModalDialog();
    configureDialog(singleUseDialog);
    singleUseDialog.show();
}

void configureDialog(Dialog singleUseDialog) {
    singleUseDialog.setDimensions(400, 300);
}

これは有効なパターンである可能性がありますが、この例では、オーバーライドまたは別の場所でこのコードを再利用する場合を除き、configureDialog()メソッドをインライン化します。


7
2番目のケースで単一行メソッドを分離することは、呼び出しメソッドの抽象化レベルの一貫性を保つために有益です。この推論が使用されている場合、提供されている例はフェンス上にあります:正確なピクセル幅と高さは、ダイアログを表示するにはあまりにも詳細ですか?
アレクシYrttiaho

2
「isNotValue()」メソッドの名前は間違っているため、実際のドメインの質問に名前を付けるためにリファクタリングする必要があります。

4
@アレクシ:私は同意しません。この例では、詳細はすでにshowDialog()メソッド内に隠されています。二重にリファクタリングする必要はありません。この例は、孤立したユースケースを示すことを目的としています。異なる場合に異なるダイアログ構成でパターンが必要な場合は、パターンが確立されたら、戻ってリファクタリングするのが理にかなっています。
RMorrisey

1
@Thorbjorn:論理が明らかでないかもしれないビジネスドメインの質問に答えているとき、私はそれのケースを見ることができました。私はただ、それが行き過ぎている場合があることを指摘しています。
RMorrisey

3
たぶんこれはちょっとしたことですが、あなたの例では、問題は関数が短すぎるということではなく、説明的な価値を追加しないということです。
ケプラ

59

機能が短すぎますか?一般的にいいえ。

実際、それを保証する唯一の方法は:

  1. デザインにすべてのクラスが見つかりました
  2. あなたの機能はただ一つのことをしています。

関数をできるだけ小さくすることです。または、言い換えれば、それ以上抽出できないまで関数から関数を抽出します。これを「ドロップするまで抽出」と呼びます。

これを説明するには:関数は、変数によって通信する機能のチャンクを持つスコープです。クラスは、変数によって通信する機能のチャンクを持つスコープでもあります。したがって、長い関数は、小さなメソッドを使用して常に1つ以上のクラスに置き換えることができます。

また、別の関数を抽出するのに十分な大きさの関数は、定義により複数のことを行っています。したがって、ある関数を別の関数から抽出できる場合は、その関数抽出する必要があります。

一部の人々は、これが機能の急増につながることを心配しています。彼らは正しい。そうなる。それは実際には良いことです。関数には名前があるので、それは良いことです。適切な名前を選択することに注意している場合、これらの関数は、コードを介して他の人を誘導する標識投稿として機能します。実際、適切な名前の名前空間内の適切な名前のクラス内の適切な名前の関数は、読者が迷子にならないようにするための最良の方法の1つです。

cleancoders.comのClean CodeのエピソードIIIには、これに関する詳細があります。


5
ボブおじさん!ここでお会いできて光栄です。予想通り、素晴らしいアドバイス。私は以前に「あなたが落ちるまで抽出する」という言葉を聞いたことがないので、月曜日のオフィスでこの言葉を確実に使用するでしょう;)。
マーティンブロア

1
ポイント#1について詳しく説明してもらえますか?例によって素晴らしいことを説明する。
ダニエルカプラン

53

うわー、これらの答えのほとんどはまったく役に立ちません。

そのアイデンティティが定義である関数を書かないでください。つまり、関数名が単に英語で記述された関数のコードブロックである場合は、関数として記述しないでください。

あなたの関数conditionMetとこの他の関数を考えてくださいaddOne(私のさびたJavaScriptを許してください):

function conditionMet() { return x == condition; }

function addOne(x) { return x + 1; }

conditionMet適切な概念的定義です。addOneトートロジーです。「条件が満たされた」と言うだけではconditionMetconditionMetが必要なのかわからないので良いのですがaddOne、英語で読むとなぜ愚かなのかがわかります。

"For the condition to be met is for x to equal condition" <-- explanatory! meaningful! useful!

"To add one to x is to take x and add one." <-- wtf!

まだ聖なるものへの愛のために、トートロジー関数を書かないでください!

(そして同じ理由で、コードのすべての行にコメントを書かないください!)


より複雑な言語構成要素の中には、なじみのないものや読みにくいものがあり、便利なトリックです。

3
同意します。正確に関数にすべきことは、それが書かれている言語に大きく依存します。addOneのような関数は、VHDLのような言語を使用している場合に役立ちます。バイナリまたは数値理論レベル。私は実践的なプログラマでも読むのが難しいほど謎めいた言語はないと思いたいです(おそらくbrainf#ck)その言語に-名前は定義と同じではありません。
宮坂

1
Haskell標準ライブラリのアイデンティティ関数について話しています-それよりも「トートロジー」を得ることができるとは思わない:)
hugomg

1
フン@missingno、それは浮気だ:D
レイ宮坂

5
関数の内容ではなく目的で関数に名前を付けると、それらはすぐにばかげているのをやめます。だからあなたの例があることなどであるかもしれないfunction nametoolong() { return name.size > 15; }か、function successorIndex(x) { return x + 1; } だからあなたの関数の問題点は、その名前が悪いだけのことを実際にあります。
ハンスピーターシュトルー

14

コメントを追加することでコードの意図を改善できると思う場合は、そのコメントを追加するのではなく、独自のメソッドにコードを抽出します。コードがどれほど小さくても。

たとえば、コードが次のようになっている場合:

if x == 1 { ... } // is pixel on?

代わりに次のようにします。

if pixelOn() { ... }

function pixelOn() { return x == 1; }

または、言い換えると、メソッドの長さではなく、自己文書化コードについてです。


5
私の尊敬する同僚は、あなたも書くことができると指摘していますif x == PIXEL_ON。定数を使用すると、メソッドを使用するのと同じくらいわかりやすく、少し簡潔になります。
トムアンダーソン

7

これがまさにあなたがやりたいことだと思います。現在、その機能は1行または2行だけかもしれませんが、時間が経つにつれて大きくなる可能性があります。また、より多くの関数呼び出しがあると、関数呼び出しを読んで、そこで何が起こっているのかを理解できます。これにより、コードが非常に乾燥した(自分自身を繰り返さない)ことができます。


4
それで、後でそれを因数分解することの何が問題なのか、今ではなく、ただの重さである必要があると証明できるとき?
dsimcha

関数はそれ自体では成長しません。私見の例はお粗末です。conditionMetはあまりにも一般的な名前であり、引数を取らないので、いくつかの状態をテストしますか?(x == xCondition)は、ほとんどの言語および状況で「xConditition」と同じくらい表現力豊かです。
ユーザー不明

はい、それがあなたのコードに75%以上のオーバーヘッドを持ちたい理由です?3行として書かれたライナーは実際には300%です。
コーダー

5

私が見た他のすべての投稿に同意します。これは良いスタイルです。

このような小さなメソッドのオーバーヘッドは、オプティマイザーが呼び出しを最適化し、コードをインライン化するため、ゼロになる場合があります。このような単純なコードは、オプティマイザーが最善の仕事をすることを可能にします。

コードは明快さと単純さのために書かれるべきです。メソッドを2つの役割のいずれかに限定しようとしています。または作業を実行します。これにより、1行のメソッドが生成される場合があります。これを上手にやればやるほど、コードはより良くなります。

このようなコードは、凝集度が高く、結合度が低い傾向があるため、適切なコーディング方法です。

編集:メソッド名に関する注意。メソッドが実行する方法ではなく、実行する方法を示すメソッド名を使用します。verb_noun(_modifier)が適切な命名スキームであることがわかりました。これにより、Select_Customer_Using_NameIdxではなくFind_Customer_ByNameなどの名前が付けられます。2番目のケースは、メソッドが変更されたときに不正確になる傾向があります。最初のケースでは、顧客データベースの実装全体を交換できます。


インライン化に言及するための+1、パフォーマンスと短い関数の主な考慮事項。
ガレットクラボーン

4

1行のコードを関数にリファクタリングするのは過度に思えます。ver loooooong / comples行やエクスペションなどの例外的なケースがあるかもしれませんが、機能が将来成長することがわかっている場合を除き、これを行いません。

最初の例では、グローバルの使用に関するヒント(コード内の他の問題について言及する場合もしない場合もあります)をさらにリファクタリングし、これら2つの変数をパラメーターとして作成します。

function conditionMet(x, condition){
   return x == condition;
}
....
conditionMet(1,(3-2));
conditionMet("abc","abc");

このconditionMet、次のように条件が長く繰り返される場合に役立ちます。

function conditionMet(x, someObject){
   return x == ((someObject.valA + someObject.valB - 15.4) / /*...whole bunch of other stuff...*/);
}

1
彼の最初の例は、グローバルのヒントではありません。どちらかといえば、それは、ほとんどの変数がオブジェクト上にある、非常にまとまりのあるクラスのまとまりのあるメソッドです。
マーティンブロア

7
そのconditionMet関数は単なる冗長==演算子です。それはおそらく有用ではありません。

1
私は間違いなくグローバルを使用しません。@MeshMan、それは例がクラスのメソッドから...
マークブラウン

1
@マークブラウン:@MeshMan:わかりました、コード例が曖昧すぎて確実に知ることができません...-
FrustratedWithFormsDesigner

conditionMet (x, relation condition) {ここで、x、 '=='、および関係を渡す場所の匂いがします。次に、「<」、「>」、「<=」、「!=」などのコードを繰り返す必要はありません。一方、ほとんどの言語では、演算子(x ==条件)がそれを行うため、これらの構造が組み込まれています。アトミックステートメントの関数は、一歩先のことです。
ユーザー不明

3

このことを考慮:

簡単な衝突検出機能:

bool collide(OBJ a, OBJ b)
{
    return(pow(a.x - b.x, 2) + pow(a.y - b.y, 2) <= pow(a.radius + b.radius, 2));
}

コード内に常に「単純な」1つのライナーを記述した場合、最終的に間違いを犯す可能性があります。さらに、それを何度も書くのは本当に拷問になります。


ここでCを扱っている場合、単に悪用することができ#defineます;)
コールジョンソン

サイズや距離が非常に大きくなった場合、これをより低速であるがオーバーフローしないハイポと交換することもできます。これは明らかに、一度行うのがずっと簡単です。
マットクラウス

2

いいえ、それはめったに問題ではありません。今、誰かが関数がコードの1行よりも長くはならないと感じた場合(それがそんなに単純なことができる場合のみ)、それは問題であり、彼らは適切なものを考えていないため、ある意味で怠け者になります。


コードの行ではなく、式でカウントします。「(X ==条件)」アトミックなので、それは複数回使用されるなど、変更される可能性がある場合、私は、唯一の方法/機能に入れてしまうfunction isAdult () = { age >= 18; }と、しかし確実ではありませんisAtLeast18
ユーザー不明

2

短すぎると思いますが、これは私の主観的な意見です。

なぜなら:

  • 1回または2回しか使用されない関数を作成する理由はありません。defs suckへのジャンプ。特に驚くほど高速なVSおよびC ++コードの場合。
  • クラスの概要。あなたが何千もの小さな機能を持っているとき、それは私を怒らせます。クラス定義を表示して、SetXToOne、SetYToVector3、MultiplyNumbers、+ 100個のセッター/ゲッターがどのように機能するかではなく、クラスの機能をすぐに確認できるのが楽しみです。
  • ほとんどのプロジェクトでは、これらのヘルパーは1つまたは2つのリファクタリングフェーズの後に重荷になります。その後、「すべて検索」->削除を実行して、廃止コード(通常は約25%以上)を削除します。

長い関数は悪いですが、3行より短く、1つだけを実行する関数も同様に悪いです。

だから私はそれが次の場合にのみ小さな関数を書くと言うでしょう:

  • 3行以上のコード
  • ジュニア開発者が見逃す可能性のあるもの(わからない)
  • 追加の検証を行います
  • 使用される、または少なくとも3倍使用される
  • 頻繁に使用されるインターフェースを簡素化
  • 次のリファクタリング中に重荷になりません
  • テンプレートの特殊化など、特別な意味があります
  • いくつかの分離ジョブを行います-const参照、可変パラメーターに影響し、プライベートメンバーの取得を行います

次の開発者(シニア)は、SetXToOneのすべての関数を覚えるよりも、やるべきことがあると思います。いずれにしても、彼らはすぐに死に変わるでしょう。


1

私は例が好きではありません。1、i番目の一般名のため。

conditionMetジェネリックではないようですので、特定の条件を表しますか?好む

isAdult () = { 
  age >= 18 
}

これでいいでしょう。意味の違いですが、

isAtLeast18 () { age >= 18; } 

私には大丈夫ではないでしょう。

たぶんそれは頻繁に使用され、後で変更される可能性があります:

getPreferredIcecream () { return List ("banana", "choclate", "vanilla", "walnut") }

大丈夫です 複数回使用すると、必要に応じて1つの場所を変更するだけで済みます。明日はホイップクリームが可能になるかもしれません。

isXYZ (Foo foo) { foo.x > 15 && foo.y < foo.x * 2 }

アトミックではないので、良いテストの機会を与えるはずです。

関数を渡す必要がある場合は、もちろん、好きなものを渡し、それ以外の場合は愚かな外観の関数を作成します。

しかし、一般的に、短すぎる関数よりも長すぎる関数が多く見られます。

最後の言葉:一部の関数は冗長に書かれているため、適切に見えるだけです。

function lessThan (a, b) {
  if (a < b) return true else return false; 
}

あなたが見るなら、それは同じであると

return (a < b); 

あなたには問題はありません

localLessThan = (a < b); 

の代わりに

localLessThan = lessThan (a, b); 

0

コードなしのようなコードはありません!

シンプルに保ち、複雑になりすぎないようにしてください。

それは怠beingではなく、あなたの仕事をしています!


0

はい、短いコード機能を使用しても大丈夫です。「ゲッター」、「セッター」、「アクセサー」などのメソッドの場合、以前の回答で述べたように、非常に一般的です。

時々、これらの短い「アクセサ」関数は仮想的です。サブクラスでオーバーライドされると、関数はより多くのコードを持つためです。

多くの関数で、グローバルでもメソッドでも、それほど短くなく機能したい場合は、通常、直接リターンの代わりに「結果」変数(パスカルスタイル)を使用します。これは、デバッガを使用するときに非常に役立ちます。

function int CalculateSomething() {
  int Result = -1;

   // more code, maybe, maybe not

  return Result;
}

0

短すぎることは決して問題ではありません。短い機能のいくつかの理由は次のとおりです。

再利用性

たとえば、setメソッドなどの関数がある場合、パラメーターが有効であることを確認するために関数をアサートできます。このチェックは、変数が設定されているすべての場所で実行する必要があります。

保守性

将来変更される可能性があると思われるステートメントを使用する場合があります。たとえば、列にシンボルを表示するようになりましたが、後でそれが別のもの(またはビープ音)に変わる可能性があります。

均一

たとえば、ファサードパターンを使用しており、関数が行う唯一のことは、引数を変更せずに関数を別の関数に正確に渡すことです。


0

コードセクションに名前を付けるとき、それは本質的にそれに名前を付けるためです。これにはいくつかの理由がありますが、重要な点は、プログラムに追加する意味のある名前を付けることができるかどうかです。「addOneToCounter」のような名前は何も追加しませんが、追加しconditionMet()ます。

スニペットが短すぎるか長すぎるかを見つけるためのルールが必要な場合は、スニペットの意味のある名前を見つけるのにかかる時間を検討してください。妥当な時間内にできない場合、スニペットは適切なサイズではありません。


0

いいえ、しかしそれはあまりにも簡潔になる可能性があります。

要確認:コードは1回書かれますが、何度も読みます。

コンパイラ用のコードを書かないでください。コードを保守する必要がある将来の開発者向けに作成してください。


-1

はい、機能はますます小さくなり、良いか悪いかは使用している言語/フレームワークに依存します。

私の意見では、主にフロントエンドテクノロジで作業しています。小さな関数は主にヘルパー関数として使用されます。アプリケーションに共通のロジックが多すぎる場合、たくさんの小さな関数が作成されます。

ただし、共通のロジックを持たないアプリケーションでは、小さな関数を作成する必要はありませんが、コードをセグメントに分割して、管理および理解しやすくすることができます。

一般に、巨大なコードを小さな関数に分割することは非常に良いアプローチです。最新のフレームワークと言語では、そうする必要があります。

data => initScroll(data)

ES 2017 JavaScriptおよびTypescriptの匿名関数です

getMarketSegments() {
 this.marketService.getAllSegments(this.project.id)
  .subscribe(data => this.segments = data, error => console.log(error.toString()));
}

上記のコードでは、3つの関数宣言と2つの関数呼び出しを見ることができます。これは、Angular 4のTypescriptを使用した単純なサービス呼び出しです。あなたの要件としてそれを考えることができます

([] 0)
([x] 1)
([x y] 2)

上記はclojure言語の3つの匿名関数です

(def hello (fn [] "Hello world"))

上記はclojureの機能宣言です

はい

incrementNumber(numb) { return ++numb; }

そうするのは良い習慣ではありませんが、Angular FrameworkのようにHTMLタグでこの関数を使用している場合、Angular HTMLテンプレートでIncrementまたはDecrementがサポートされていない場合は、これが解決策になります私。

別の例を見てみましょう

insertInArray(array, newKey) {
 if (!array.includes(newKey)) {
   array.push(newKey);
 }
}

上記の例は、Angular HTMLテンプレート内の配列を再生する際に必須です。そのため、小さな関数を作成する必要がある場合があります


-2

私は、現時点で与えられたほとんどの答えに同意しません。

最近、次のようなすべてのクラスメンバーを作成する同僚を見つけました。

 void setProperty(int value){ mValue=value; }
 int getProperty() const { return (mValue); }

これは散発的な場合には良いコードかもしれませんが、多くのプロパティを持つ多くのクラスを定義する場合は間違いありません。これで、上記のようなメソッドは書かれないとは言いたくありません。しかし、ほとんどのコードを実際のロジックの「ラッパー」として書くことになった場合、何か問題があります。

おそらくプログラマーは全体的なロジックを見逃し、将来の変更やコードのリファクタリングを恐れます。

インターフェイスの定義は、本当に必要なためです。実際、単純なプロパティを設定して取得する必要がある場合(多くのロジックがないため、メンバーが短くなります)、それは可能です。しかし、実際のニーズを別の方法で分析できる場合、より直感的なインターフェイスを定義しないのはなぜですか?

質問に本当に答えるために、もちろんそれはいいえです、そして、理由は非常に明白です:方法/プロパティ/ whatelseはそれが必要なもののために定義されなければなりません。空の仮想関数であっても存在する理由があります。


6
アクセサー/ミューテーターメソッドを追加する正当な理由があります。これは、「将来についての恐怖...リファクタリング」で示唆しています。しかし、コードに価値を追加しないことも正しいです。サイズを(ローカルまたはグローバルに)縮小したり、可読性を高めたりすることはできません。私の考えでは、これは、JavaとJavaBeansの両方の規約における貧弱な言語設計を物語っています。これは、C#が自動プロパティ構文に関する両方の懸念に対処するために言語を正常に改善した(多くの)1つの領域です。Javaプログラマーとして、基本的な構造体のようなクラスの生のスタイルに同意します。
-Anm

1
ゲッター/セッター関数は、変数の読み取りまたは書き込みのみを行う場合でも有効です。実際問題として、後でフィールドが変更されるたびに画面に値を印刷するか、その値が有効かどうかを確認するシナリオを想像できます。(おそらく分数の分母であり、ゼロでないことを確認する必要があります。)
宮坂

2
ゲッターとセッターはひどいですが、それらの有効な使用にもかかわらずです。それらを使用する必要がある場合、私はそれが言語の失敗だと思います。
カーソンマイヤーズ

@Rei:外部クラスが分母の値をチェックする必要があるのはなぜですか?これは、FractionクラスのDivide()関数によって行われる必要があります。または、Fractionクラスは、null分母をチェックするisDivisible()を提供する必要があります。ゲッター/セッターを必要とするのは、通常、クラスの結合度が高く、凝集度が低い(つまり悪い)コード臭です。
ライライアン

@Lie何?分母をチェックするために外部クラスを使用すべきだとは決して言いませんでした。外部クラスが偶然分母を0に設定するかもしれないと言っています。セッターで有効性チェックを行う目的は、消費者コードのバグを早期に発見できるようにすることです。呼び出される場合と呼び出されない場合があるisDivisible()関数を提供しても、そのためには機能しません。また、ゲッター/セッターの必要性は、高い結合があることをどのように正確に示していますか?
宮坂
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.