2回繰り返す必要があるもののために関数を書くことは常にベストプラクティスですか?


131

私自身、何かを2回以上行う必要がある場合、関数を書くのを待つことができません。しかし、2回しか表示されないものに関しては、少し注意が必要です。

3行以上を必要とするコードの場合、関数を作成します。しかし、次のようなものに直面するとき:

print "Hi, Tom"
print "Hi, Mary"

私は書くのをためらっています:

def greeting(name):
    print "Hi, " + name

greeting('Tom')
greeting('Mary')

2番目のものは多すぎるようですね。


しかし、私たちが持っている場合:

for name in vip_list:
    print name
for name in guest_list:
    print name

そして、代替手段は次のとおりです。

def print_name(name_list):
    for name in name_list:
        print name

print_name(vip_list)
print_name(guest_list)

物事はトリッキーになりますか?今決めるのは難しい。

これについてあなたはどう思いますか?


145
私は治療alwaysnever赤旗など。「コンテキスト」と呼ばれる事、ありますalwaysneverしても、一般的に良い場合のルールは、適切なことではないかもしれないが。絶対に対処するソフトウェア開発者に注意してください。;)
非同期

7
最後の例では、次のことができますfrom itertools import chain; for name in chain(vip_list, guest_list): print(name)
バクリウ

14
@ user16547私たちはそれらを「シットウェア開発者」と呼んでいます!
ブライアン

6
ティムピーターズによるPythonの禅から:Special cases aren't special enough to break the rules. Although practicality beats purity.
ゼノン

3
また、あなたは「湿っコード」または「ドライコード」をしたいかどうかを確認考えるstackoverflow.com/questions/6453235/...
イアン

回答:


201

関数を分割することを決定する際の1つの要因ですが、何かが繰り返される回数が唯一の要因ではないはずです。一度だけ実行される何かのために関数を作成することはしばしば意味があります。通常、次の場合に関数を分割します。

  • 個々の抽象化レイヤーを簡素化します。
  • 分割関数には適切で意味のある名前があるので、通常は何が起こっているかを理解するために抽象化レイヤー間を飛び回る必要はありません。

あなたの例はその基準を満たしていません。あなたはワンライナーからワンライナーに行きます、そして、名前は明確さの点であなたに何も買わないのです。そうは言っても、単純な機能は、チュートリアルや学校の割り当て以外ではまれです。ほとんどのプログラマーは、他の方法でエラーを出しすぎます。


これが、RobertHarveyの答えで見落としている点です。
ドックブラウン

1
確かに。複雑な機能をインラインサブルーチンに分割することがよくあります。パフォーマンスヒットはありませんが、クリーンです。匿名スコープも良いです。
イマレット

3
スコープの問題もあります。クラス全体またはモジュール全体が見ることができるprint_name(name_list)関数を記述することはおそらく意味がありませんが、(この場合はまだまだ)関数内のローカル関数をクリーンアップする意味があるかもしれません数行の繰り返しコード。
ジョシュアテイラー

6
追加したいもう1つの点は、標準化です。最初の例では挨拶が行われていますが、これを関数に組み込むことは理にかなっているので、両方の人に同じ挨拶をしていることが確実です。特に挨拶を考慮すると、:)将来変更される可能性
Svish

1
+1のためだけ一度だけ実行される何かのための関数を作成することはしばしば理にかなっています。かつて、ロケットエンジンの動作をモデル化した関数のテストを設計する方法を尋ねられました。その関数の循環的な複雑さは90年代にあり、90のケースがあるswitchステートメントではありませんでした(ugいですがテスト可能)。代わりに、エンジニアによって書かれた複雑な混乱であり、まったくテストできませんでした。私の回答は、テストが不可能であり、書き直す必要があるということだけでした。彼らは私のアドバイスに従った!
デビッドハンメン

104

複製が偶然ではなく意図的なものである場合のみ。

または、別の言い方をします:

将来それらが共進化することを期待する場合のみ。


その理由は次のとおりです。

時には、コードの2枚はちょうど起こる、彼らはお互いに何の関係もないにもかかわらず、同じになること。その場合、それらを結合する衝動に抵抗する必要あります。次回誰かがそれらのいずれかでメンテナンスを実行すると、その人は変更が以前に存在しない呼び出し元に伝播することを期待せ、その結果その機能が壊れる可能性があるためです。したがって、コードサイズを縮小するように思われるときではなく、意味のある場合にのみコードを除外する必要があります。

経験則:

コードが新しいデータのみを返し、既存のデータを変更しない、または他の副作用がない場合、別の関数としてファクタリングすることは非常に安全です。(関数の意図するセマンティクスを完全に変更せずに破損を引き起こすシナリオを想像することはできません。その時点で、関数名または署名も変更する必要があります、とにかくその場合には注意する必要があります。 )


12
+1これが最も重要な考慮事項だと思います。変更が最後であることを期待してコードをリファクタリングする人はいないため、変更がコードの将来の変更にどのように影響するかを検討してください。
yoniLavi

2
それはルビーですが、Avdi Grimmからの偶発的な複製に関するこのスクリーンキャストはまだ質問に関係しています:youtube.com/watch
DGM

60

いいえ、常にベストプラクティスとは限りません。

他のすべてのものが等しい場合、線形の行ごとのコードは、関数呼び出しをジャンプするよりも読みやすくなります。自明ではない関数呼び出しは常にパラメーターを受け取るため、すべてを整理して、関数呼び出しから関数呼び出しへのメンタルコンテキストのジャンプを行う必要があります。あいまいな理由(必要なパフォーマンスの向上を得るなど)がない限り、常にコードの明瞭さを優先してください。

では、なぜコードは個別のメソッドにリファクタリングされるのですか? モジュール性を向上させるため。個々のメソッドの背後にある重要な機能 を収集し、意味のある名前を付けるため。それを達成していない場合は、これらの個別のメソッドは必要ありません。


58
コードの読みやすさが最も重要です。
トムロビンソン

27
関数を作成する主な理由の1つを忘れていない場合、この答えを支持します。つまり、機能に適切な名前を付けて抽象化作成ます。その点を追加した場合、線形の行ごとのコードは、整形式の抽象化を使用するコードよりも常に読みやすいとは限らないことは明らかです。
Doc Brown

17
Doc Brownに同意します。コードは、適切に抽象化された場合よりも、必ずしも行ごとに読みやすいとは限りません。適切な関数名を使用すると、高レベルの関数は非常に読みやすくなり(実装ではなく意図を読むため)、低レベルの関数は読みやすくなります。これにより、関数が簡潔かつ正確になります。
ジョンストーリー

19
Why take the performance hit of a function call when you don't get any significant benefit by doing so?どのようなパフォーマンスがヒットしましたか?一般的な場合、小さな関数はインライン化され、大きな関数のオーバーヘッドは無視できます。CPythonはおそらくインライン化されませんが、パフォーマンスのためにCPythonを使用する人はいません。私もline by line code...少し質問します。脳内で実行をシミュレートしようとする方が簡単です。しかし、デバッガーはより良い仕事をするでしょう、そして、実行に従うことによってループについて何かを証明しようとすることは、それらのすべてを反復することによって整数についてのステートメントを証明しようとするようなものです。
ドーバル

6
@Dovalは非常に有効なポイントを発生させます-コードがコンパイルされると、関数はインラインに配置され(そして複製され)、コンパイル時に小さなものがありますが、実行時のパフォーマンスヒットはありません。これはインタープリター言語では当てはまりませんが、それでも関数呼び出しは実行時間のほんの一部です。
ジョンストーリー

25

あなたの特定の例では、関数を作成するのはやり過ぎに見えるかもしれません。代わりに、この質問をします。この特定の挨拶が将来変更される可能性はありますか?どうして?

関数は、機能をまとめて再利用を容易にするためだけでなく、変更を容易にするためにも使用されます。要件が変更された場合、コピーペーストされたコードを探して手動で変更する必要がありますが、関数を使用するとコードを変更する必要があるのは1回だけです。

ロジックはほとんど存在しないため、関数からではなく、この例の利点が得られますが、場合によってはGREET_OPENING定数になります。次に、この定数をファイルからロードして、プログラムをさまざまな言語に簡単に適応できるようにします。これは粗雑なソリューションであり、より複雑なプログラムでは、おそらくi18nを追跡するためのより洗練された方法が必要になることに注意してください。

最終的に考えられる要件と、将来の自分の仕事を楽にするための事前の計画がすべてです。


GREET_OPENING定数を使用すると、考えられるすべての挨拶が同じ順序であると想定されます。母国語が英語とほとんど反対のことをしている人と結婚していることは、この仮定に不安を抱かせます。
ローレンペクテル

22

個人的には、3つのルールを採用しています。これは、YAGNIを呼び出すことで正当化できます。そのコードを書いてから何かをする必要がある場合、コピー/貼り付けを2回するだけです(はい、コピー/貼り付けを認めただけです!)繰り返しになりますが、そのチャンクを独自のメソッドにリファクタリングして抽出します。私、それが再び必要になることを実証しました。

カールビーレフェルトとロバートハーベイが言ったことに同意し、彼らが言っていることの私の解釈は、最も重要なルールは読みやすさであるということです。コードが読みやすくなる場合は、関数の作成を検討してください。DRYSLAPなどを覚えておいてください。関数の抽象化レベルの変更を避けることができる場合、頭の中で管理するのが簡単であることがわかります。そのため、関数間をジャンプしない(関数の名前を読むだけで関数の動作を理解できない場合)とは、スイッチの数が少ないことを意味します精神的プロセス。
同様に、などの機能やインラインコードの間でコンテキストを切り替えることではないprint "Hi, Tom"、私のために働く、この場合、私は可能性がある機能の抽出PrintNames() IFFを 私の全体的な機能の残りはほとんどが関数呼び出しでした。


3
c2 wikiには、3つのルールに関する良いページがあります:c2.com/cgi/wiki?RuleOfThree
pimlottc

13

明確なことはめったにないので、オプションを比較検討する必要があります。

  • 期限(できるだけ早くサーバールームの書き込みを修正)
  • コードの読みやすさ(どちらの場合も選択に影響する可能性があります)
  • 共有ロジックの抽象化レベル(上記に関連)
  • 再利用の要件(つまり、まったく同じロジックが重要である、またはちょうど今便利です)
  • 共有の難しさ(ここでpythonが輝いている、以下を参照)

C ++では、通常3つのルールに従います(つまり、3回目に同じものが必要になり、適切に共有可能な部分にリファクタリングします)が、経験を積むと、スコープについて詳しく知っているので、最初にその選択をする方が簡単です。使用しているソフトウェアとドメイン。

ただし、Pythonでは、ロジックを繰り返すのではなく、再利用するのがかなり軽量です。他の多くの言語(少なくとも歴史的に)よりも、IMOです。

したがって、ローカル引数からリストを作成するなどして、ロジックをローカルで再利用することを検討してください。

def foo():
    for name_list in (vip_list, guest_list): # can be list of tuples, for many args
        for name in name_list:
            print name

複数の引数が必要な場合は、タプルのタプルを使用し、forループに直接分割できます。

def foo2():
    for header, name_list in (('vips': vip_list), ('people': guest_list)): 
        print header + ": "
        for name in name_list:
            print name

または、ローカル関数を作成します(2番目の例かもしれません)。これにより、ロジックが明示的に再利用されますが、関数の外部でprint_name()が使用されていないことも明確に示されます。

def foo():
    def print_name(name_list):
        for name in name_list:
            print name

    print_name(vip_list)
    print_name(guest_list)

関数は、ブレークや例外が不必要に物事を混乱させる可能性があるため、特に途中でロジックを中止する必要がある場合(つまり、リターンを使用する場合)に適しています。

どちらも同じロジックIMOを繰り返すよりも優れており、1つの呼び出し元のみが使用するグローバル/クラス関数を宣言するよりも混乱が少ない(ただし2回)。


この答えを見る前に、新しい関数の範囲について同様のコメントを投稿しまし。質問のその側面に対処する答えがあることを嬉しく思います。
ジョシュアテイラー

1
+1-これはおそらくOPが探している種類の答えではありませんが、少なくとも彼が尋ねた種類の質問に関しては、これは最も賢明な回答だと思います。これは、おそらく何らかの形であなたのために言語に焼き付けられるようなものです。
パトリックコリンズ

@PatrickCollins:投票してくれてありがとう!:)答えをより完全にするために、いくつかの考慮事項を追加しました。
マッケ

9

すべてのベストプラクティスには特定の理由があり、そのような質問に答えるために参照できます。潜在的な将来の変更が他のコンポーネントへの変更も意味する場合、波紋を常に回避する必要があります。

したがって、あなたの例では:あなたが標準的な挨拶を持っていると仮定し、それがすべての人に同じであることを確認したい場合は、

def std_greeting(name):
    print "Hi, " + name

for name in ["Tom", "Mary"]:
    std_greeting(name)   # even the function call should be written only once

そうしないと、たまたま変更された場合、注意を払って、2つの場所で標準グリーティングを変更する必要があります。

ただし、「Hi」が偶然同じであり、一方の挨拶を変更しても必ずしも他方の挨拶が変更されない場合は、それらを別々にしてください。したがって、次の変更の可能性が高いと考える論理的な理由がある場合は、それらを分離してください。

print "Hi, Tom"
print "Hello, Mary"

2番目の部分では、関数に「パッケージ化」する量を決定することは、おそらく2つの要因に依存します。

  • 読みやすいようにブロックを小さく保つ
  • 数ブロックだけで変更が発生するように、ブロックを十分に大きくしてください。呼び出しパターンを追跡するのが難しいため、あまりクラスター化する必要はありません。

理想的には、変更が発生すると、「次のブロックのほとんどを変更する必要があります」ではなく、「大きなブロック内のどこかでコードを変更する必要がある」と考えるでしょう。


あなたのループを考慮したばかげた選択-本当に少数のハードコードされた名前だけを繰り返し処理する必要があり、それらが変更されることを期待していない場合、私はそれを使用しない方が少し読みやすいリスト。 for name in "Tom", "Mary": std_greeting(name)
yoniLavi

まあ、続けるには、ハードコードされたリストはコードの途中に表示されず、とにかく変数になります:)しかし、右、中括弧をスキップできることを実際に忘れていました。
ジェレヌク

5

名前付き定数と関数の最も重要な側面は、入力の量を減らすほどではなく、使用されるさまざまな場所を「付加」することです。例に関して、次の4つのシナリオを検討してください。

  1. 両方の挨拶を同じ方法で変更する必要があります(例:to Bonjour, TomおよびBonjour, Mary)。

  2. 1つの挨拶を変更する必要がありますが、他の挨拶はそのままにします(例:Hi, TomおよびGuten Tag, Mary)。

  3. 両方のグリーティングを異なる方法で変更する必要があります[eg Hello, TomHowdy, Mary]。

  4. どちらの挨拶も変更する必要はありません。

どちらの挨拶も変更する必要がない場合、実際にどのアプローチをとるかは重要ではありません。共有機能を使用すると、挨拶を変更しても同じように変更されるという自然な効果があります。すべての挨拶が同じように変更される場合、それは良いことです。ただし、そうでない場合は、各人の挨拶をコーディングまたは個別に指定する必要があります。それらに共通の関数または仕様を使用させるために行われた作業は、元に戻す必要があります(そもそも行わないほうがよいでしょう)。

確かに、将来を常に予測できるとは限りませんが、両方の挨拶を一緒に変更する必要がある可能性が高いと信じる理由がある場合、それは共通のコード(または名前付き定数)を使用することを支持する要因になります; 片方または両方が異なるように変更する必要がある可能性が高いと信じる理由がある場合、それは一般的なコードを使用しようとすることに対する要因になります。


私はこの答えが好きです(少なくともこの単純な例では)、ゲーム理論によって選択をほぼ計算できるからです。
ラバーダック

@RubberDuck:多くの点で、「エイリアス」をすべきか、すべきでないかという質問にはあまり注意が払われていないと思います。バグの最も一般的な2つの原因は、(1)エイリアス/アタッチされていない場合、または(2)他のものがアタッチされていることを認識せずに何かを変更する。このような問題は、コード構造とデータ構造の両方の設計で発生しますが、この概念に多くの注意が向けられたことは今まで覚えていません。エイリアシングは、通常、何かが1つしかない場合、または変更されない場合は重要ではありませんが、
...-supercat

@RubberDuck:... 2つの何かがあり、それらが一致する場合、一方を変更すると他方の値を一定のままにするか、関係を一定に保つか(他方の値が変更されることを意味する)を知ることが重要です。値を一定に保つことの利点には大きな重みがありますが、関係の重要性にははるかに低い重みがあります。
スーパーキャット

同意します。私は従来の知恵がどのようにそれを乾かすと言っているかについて考えていましたが、統計的には、繰り返されたコードが後で繰り返されない可能性が高くなります。基本的に2対1のオッズがあります。
ラバーダック

@RubberDuck:切り離しが重要である2つのシナリオと、添付ファイルの方が良いシナリオでは、発生するさまざまなシナリオの相対的な可能性については何も述べていません。重要なのは、2つのコードが「偶然に」一致する場合、またはそれらが同じ基本的な意味を持っているためです。さらに、3つ以上のアイテムがある場合、1つ以上を他のアイテムとは異なるものにしたいが、同じ方法で2つ以上を変更したいという追加のシナリオが発生します。また、アクションが1つの場所でのみ使用されている場合でも
...-supercat

5

プロシージャを作成する理由があるべきだと思います。プロシージャを作成する理由はいくつかあります。私が最も重要だと思うものは:

  1. 抽象化
  2. モジュール性
  3. コードナビゲーション
  4. 一貫性の更新
  5. 再帰
  6. 検査

抽象化

プロシージャを使用すると、アルゴリズムの特定のステップの詳細から一般的なアルゴリズムを抽象化できます。適切に一貫性のある抽象化を見つけることができれば、これはアルゴリズムが何をするかについて考える方法を構築するのに役立ちます。たとえば、リストの表現を必ずしも考慮する必要なく、リストで機能するアルゴリズムを設計できます。

モジュール性

プロシージャを使用して、コードをモジュールに編成できます。多くの場合、モジュールは個別に処理できます。たとえば、個別に構築およびテストします。

適切に設計されたモジュールは、通常、一貫性のある意味のある機能単位をキャプチャします。そのため、多くの場合、異なるチームが所有したり、別のチームに完全に置き換えたり、異なるコンテキストで再利用したりできます。

コードナビゲーション

大規模なシステムでは、特定のシステムの動作に関連付けられたコードを見つけるのは難しい場合があります。ライブラリ、モジュール、およびプロシージャ内のコードの階層構造は、この課題に役立ちます。特に、a)意味のある予測可能な名前で機能を整理する場合、b)同様の機能に関連する手順を互いに近くに配置します。

一貫性の更新

大規模なシステムでは、動作の特定の変更を実現するために変更する必要があるすべてのコードを見つけるのは難しい場合があります。手順を使用してプログラムの機能を整理すると、これが簡単になります。特に、プログラムの各機能が一度だけ表示され、まとまりのある手順のグループに含まれている場合、(私の経験では)更新したはずの場所を見失ったり、一貫性のない更新を行ったりする可能性が低くなります。

プログラムの機能と抽象化に基づいて手順を整理する必要があることに注意してください。現時点で2ビットのコードが同じであるかどうかに基づいていません。

再帰

再帰を使用するには、プロシージャを作成する必要があります

テスト中

通常、プロシージャは互いに独立してテストできます。通常、プロシージャの最初の部分を2番目の部分の前に実行する必要があるため、プロシージャの本体のさまざまな部分を個別にテストすることはより困難です。この観察は、プログラムの動作を指定/検証する他のアプローチにもよく当てはまります。

結論

これらのポイントの多くは、プログラムの理解性に関連しています。プロシージャは、ドメイン固有の新しい言語を作成し、ドメイン内の問題とプロセスについて整理、書き込み、読み取りを行うための方法であると言えます。


4

どちらの場合も、リファクタリングする価値はないようです。

どちらの場合も、明確かつ簡潔に表現された操作を実行しているため、一貫性のない変更が行われる危険はありません。

コードを分離する方法を常に探すことをお勧めします:

  1. そこが析出可能性の識別、意味のあるタスクがあり、かつ

  2. どちらか

    a。タスクは表現するのが複雑です(利用可能な機能を考慮に入れる)または

    b。タスクが複数回実行され、両方の場所で同じように変更されるようにする方法が必要です。

条件1が満たされていない場合、コードの適切なインターフェイスが見つかりません。強制しようとすると、パラメーターのロード、返される複数の項目、多くのコンテキストロジックが発生します。良い名前が見つかりません。最初に考えているインターフェイスを文書化してみてください。これは、機能の正しいバンドルであるという仮説をテストする良い方法であり、コードを記述するときに既に仕様と文書化されているという追加のボーナスが付いています!

2aは2bなしでも可能です:一度しか使用されないことがわかっている場合でも、コードを取り出したことがあります。単に他の場所に移動して1行に置き換えると、呼び出されたコンテキストが突然読みやすくなるためです(特に、言語が実装を困難にする簡単な概念の場合)。また、抽出された関数を読み取るときに、ロジックの開始と終了、およびその機能が明確になります。

2aはなくても2bは可能です:トリックは、どのような変更がより可能性が高いかを感じることです。会社のポリシーがどこでも「こんにちは」から「こんにちは」に変わることや、支払いの要求やサービス停止の謝罪を送る場合、挨拶はより正式な口調に変わる可能性がありますか?よくわからない外部ライブラリを使用していて、それを別のライブラリとすばやく交換できるようにしたい場合があります。機能がなくても実装が変更される可能性があります。

ただし、ほとんどの場合、2aと2bが混在しています。そして、コードのビジネス価値、使用頻度、十分に文書化され理解されているかどうか、テストスイートの状態などを考慮して、判断を下す必要があります。

同じビットのロジックが複数回使用されていることに気付いた場合は、必ずリファクタリングを検討する機会を利用してください。nほとんどの言語でインデントレベルを超える新しいステートメントを開始する場合、それはもう1つのわずかに重要度の低いトリガーです(n特定の言語で満足できる値を選択します。たとえば、Pythonは6かもしれません)。

これらのことを考えて、大きなスパゲッティコードモンスターを倒す限り、大丈夫です。リファクタリングがどれだけきめ細かく必要なのか心配しないでください。テストやドキュメンテーションのようなもののために働いた。実行する必要がある場合は、時間がわかります。


3

一般的に私はRobert Harveyに同意しますが、機能を機能に分割するケースを追加したかったです。読みやすさを向上させるため。ケースを考えます:

def doIt(smth,smthElse)
    for x in getDataFromSomething(smth,smthElse):
        if not check(x,smth):
            continue
        process(x,smthElse)
        store(x) 

これらの関数呼び出しは他のどこでも使用されていませんが、3つの関数すべてがかなり長く、ネストされたループなどがある場合、可読性が大幅に向上します。


2

適用する必要がある厳密なルールはありません。実際に使用される機能に依存します。単純な名前の印刷では、関数を使用しませんが、数学の合計であれば別の話になります。次に、2回しか呼び出されない場合でも関数を作成します。これは、数学の合計が変更されても常に同じになるようにするためです。別の例では、何らかの形式の検証を行う場合は関数を使用します。したがって、この例では、名前が5文字より長いことを確認する必要がある場合は、関数を使用して同じ検証が常に行われるようにします。

「2行以上必要なコードについては、関数を書く必要があります」と述べたときに、あなた自身の質問に答えたと思います。通常、関数を使用しますが、関数を使用することで付加価値があるかどうかを判断するには、独自のロジックも使用する必要があります。


2

ここで言及されていないリファクタリングについて何かを信じるようになりました。すでに多くの答えがあることは知っていますが、これは新しいことだと思います。

条件が発生する前から、私は冷酷なリファクタラーであり、DRYを強く信じてきました。ほとんどの場合、頭の中に大きなコードベースを保持するのに苦労していることと、DRYコーディングを楽しんでいて、C&Pコーディングについて何も楽しんでいないことが原因です。

大事なことは、DRYを主張することで、他の人が使用することはめったにない、いくつかのテクニックで多くの練習ができたことです。多くの人々は、JavaをDRYにすることは困難または不可能であると主張しますが、実際には彼らは試していないだけです。

昔の例は、あなたの例にいくらか似ています。人々は、Java GUIの作成は難しいと考える傾向があります。もちろん、次のようにコーディングした場合です。

Menu m=new Menu("File");
MenuItem save=new MenuItem("Save")
save.addAction(saveAction); // I forget the syntax, but you get the idea
m.add(save);
MenuItem load=new MenuItem("Load")
load.addAction(loadAction)

これはおかしいと思う人は絶対に正しいが、それはJavaのせいではない。コードをこのように書くべきではない。これらのメソッド呼び出しは、他のシステムでラップすることを目的とした関数です。そのようなシステムが見つからない場合は、構築してください!

あなたは明らかにそのようなコードを書くことができないので、あなたは戻って問題を見る必要があります、実際にその繰り返しコードは何をしているのですか?いくつかの文字列とそれらの関係(ツリー)を指定し、そのツリーの葉をアクションに結合しています。だからあなたが本当に欲しいのは言うことです:

class Menu {
    @MenuItem("File|Load")
    public void fileLoad(){...}
    @MenuItem("File|Save")
    public void fileSave(){...}
    @MenuItem("Edit|Copy")
    public void editCopy(){...}...

簡潔で説明的な関係を何らかの方法で定義したら、それを処理するメソッドを記述します。この場合、渡されたクラスのメソッドを反復処理してツリーを構築し、それを使用してメニューを構築しますおよびアクション(もちろん)メニューを表示します。重複はなく、メソッドは再利用可能です...そして、おそらく多数のメニューよりも簡単に書くことができ、実際にプログラミングを楽しむなら、もっと楽しくなりました。これは難しくありません。あなたが書く必要がある方法は、メニューを手で作成するよりもおそらく行が少ないでしょう!

事は、これをうまく行うために、あなたがたくさん練習する必要があるということです。繰り返し部分に含まれる固有の情報を正確に分析し、その情報を抽出して、それをうまく表現する方法を見つけ出す必要があります。文字列の解析や注釈のようなツールを使用することを学ぶことは大いに役立ちます。エラー報告とドキュメントについて本当に明確になることを学ぶことも非常に重要です。

コーディングするだけで「無料」の練習ができます。たまに、DRYのコーディング(再利用可能なツールの作成を含む)は、コピーアンドペーストやすべてのエラー、重複したエラーよりも速いことがわかります。そして、そのタイプのコーディングが引き起こす困難な変更。

DRYテクニックとツールの構築をできるだけ練習しなければ、仕事を楽しむことができるとは思いません。プログラミングをコピーアンドペーストする必要がないために給与を削減する必要がある場合は、それを採用します。

だから私のポイントは:

  • リファクタリングの方法がわからない場合を除き、コピーと貼り付けには時間がかかります。
  • 最も困難で些細な場合でもDRYを主張することで、リファクタリングをうまく行うことを学びます。
  • あなたはプログラマーです。コードをDRYにするための小さなツールが必要な場合は、ビルドしてください。

1

一般に、何かを機能の一部に分割してコードの可読性を向上させることをお勧めします。または、繰り返される部分が何らかの形でシステムのコアである場合。上記の例で、ロケールに応じてユーザーに挨拶する必要がある場合は、別の挨拶機能を使用するのが理にかなっています。


1

関数を使用して抽象化を作成することに関する@RobertHarveyへの@DocBrownのコメントの結果として:背後のコードよりも大幅に簡潔または明確ではない適切な情報関数名を思い付かない場合、あまり抽象化されていません。それを機能にする他の正当な理由がなければ、そうする必要はありません。

また、関数名が完全なセマンティクスをキャプチャすることはめったにないため、一般的に慣れているのに十分でない場合は、定義を確認する必要があります。特に関数型言語以外では、副作用があるかどうか、発生する可能性のあるエラーとその対処方法、時間の複雑さ、リソースの割り当てや解放、スレッドであるかどうかを知る必要があります。安全。

もちろん、ここでは単純な関数のみを考慮して定義していますが、それは両方の方法に当てはまります。そのような場合、インライン化は複雑さを増す可能性がありません。さらに、読者はおそらく、見るまでそれが単純な関数であることを理解しないでしょう。定義にハイパーリンクするIDEであっても、視覚的なジャンプは理解の障害となります。

一般的に言えば、コードをより多くの小さな関数に再帰的にファクタリングすると、最終的に関数が独立した意味を持たなくなるようになります。周囲のコードによって。類推として、それを絵のように考えてください。ズームインしすぎると、見ているものがわかりません。


1
関数でそれをラップする最も単純なステートメントである場合でも、より読みやすくする必要があります。関数でそれをラップするのはやり過ぎかもしれませんが、ステートメントが何をしているかよりも簡潔で明確な関数名を思い付かない場合、それはおそらく悪い兆候です。関数を使用することに対するマイナス面は、それほど大きなものではありません。インライン化のマイナス面は、はるかに大きな問題です。インライン化すると、読みにくくなり、保守しにくくなり、再利用しにくくなります。
15年

@pllee:極端な場合、関数の因数分解が逆効果になる理由を提示しました(一般的な場合ではなく、極端な場合に具体的に言及していることに注意してください)。単に「違う」と主張するだけです。ネーミングの問題が「おそらく」悪いサインであることを書くことは、ここで検討する場合に回避可能な議論ではありません(もちろん、私の議論、少なくとも警告であるということです-あなたは単にそれ自身のために抽象化します。)
sdenham

インライン化の3つのネガについては、「どこが違うべきだ」と主張しているのかわからないことを述べました。私が言っているのは、あなたが名前を思い付かない場合、あなたのインラインはおそらくやりすぎだということです。また、ここでは、一度使用された小さなメソッドについても大きなポイントを見逃していると思います。よく名付けられた小さなメソッドが存在するため、コードが何をしようとしているかを知る必要はありません(IDEをジャンプする必要はありません)。たとえば、 `if(day == 0 || day == 6)` vs `if(isWeekend(day))`の単純なステートメントでも読みやすく、精神的にマッピングしやすくなります。今、あなたがその声明を繰り返す必要があるならばisWeekend、簡単になります。
15年

@pllee繰り返されるコードフラグメントのほとんどすべてに対して大幅に短く明確な名前を見つけることができない場合、それは「おそらく悪い兆候」であると主張しますが、それは信仰の問題のようです。 isWeekendには意味のある名前があります。そのため、私の基準を満たさない唯一の方法は、実装よりも大幅に短くも明確でもない場合のみです。あなたは後者だと思うことによって、あなたはそれが私の立場の反例ではないことを主張しています(FWIW、私はisWeekendを使用しました。)
sdenham

1行のステートメントでも読みやすくすることができる例を挙げ、インラインコードのネガを与えました。それが私の謙虚な意見であり、私が言っていることすべてであり、私は「名前のない方法」に反対したり、話したりしていません。それについて何がそんなに混乱するのか、あるいはあなたが私の意見のために「理論的証明」が必要だと思う理由が本当にわかりません。読みやすさの改善を例に挙げました。コードハンクを変更する場合、N個のスポットで変更する必要があるため、メンテナンスは難しくなります(DRYについて読んでください)。コードを再利用する実行可能な方法である場合、コピーペーストを考えない限り、再利用することは明らかに困難です。
15年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.