web-dev-qa-db-ja.com

メソッドが常に値を返すようにすることは良い習慣と考えられていますか?

ひどいタイトルで申し訳ありませんが、うまくいけば、これらのスニペットがあなたに要点を与えるでしょう。

方法1:

class Person:
    def __init__(self, name):
        self.name = name

    def set_name(self, new_name):
        self.name = ' '.join(s[0].upper() + s[1:] for s in new_name.split(' '))


def __main__():
    me = Person('Foo')
    me.set_name('foo bar')

方法2:

class Person:
    def __init__(self, name):
        self.name = name

    def set_name(self, new_name):
        return ' '.join(s[0].upper() + s[1:] for s in new_name.split(' '))


def __main__():
    me = Person('Foo')
    me.name = me.set_name('foo bar')

比較的単純な例では、どちらの方法でも同じ結果が得られるはずです。私の質問は、どの方法がより良い実践と考えられるでしょうか?読みやすさやパフォーマンスの理由はありますか、それとも単に個人的な好みですか?

5
Tochi Obudulu

私が最初に学んだ言語の1つはパスカルでした。これには重要な違いがありました。 procedures があり、戻り値がありませんでした functions always に戻り値のタイプがありました。

これらは、別々のことを行う2つの別々の構成要素です。

関数は値を返します。常に。

それが邪魔にならないようになりましたが、メソッド(それがfunction-esqueでもprocedure-esqueでも)は、何かを返す場合に意味のあるものを常に返す必要があります。時間の99%または100%で破棄される値を返すことは役に立ちません-それは無駄です。優れたコンパイラはこれを認識し、リターンが何もない場合でも最小限にすることができますが、コードを読む人にとっては human 時間の無駄です。それは何を返しますか?なぜ戻ってくるのですか?

多くの静的分析ツールは、(適切に)関数呼び出しの戻り値を無視することについてプログラマーに警告します。例 FindBugs

このメソッドは、複数のバイトを返すことができるJava.io.InputStream.read()のバリアントの1つの戻り値を無視します。戻り値がチェックされていない場合、呼び出し側は、呼び出し側が要求したよりも少ないバイト数が読み取られた場合を正しく処理できません。多くのプログラムでは、入力ストリームからの読み取りは通常、要求されたデータの全量を読み取るため、これは特に油断のならない種類のバグであり、プログラムが散発的にのみ失敗する原因になります。

または g ++の警告 (#534)

関数 'Symbol'の戻り値を無視(場所と比較)

値を返す関数は、たとえばステートメント自体やコンマ演算子の左側などの副作用のためにのみ呼び出されます。試してみてください:(void)function();関数を呼び出し、その戻り値を無視します。 fvr、fvo、およびfdrフラグも参照してください。

したがって、返される戻り値をする必要がありますであることが期待されます。意味がなければ、そもそもそうであってはなりません。それが例外的な失敗だとしたら、それが例外です(多くの言語で)。

さて、このコードについて何か見てみましょう:

_class Person:
    def __init__(self, name):
        self.name = name

    def set_name(self, new_name):return ' '.join(s[0].upper() + s[1:] for s in new_name.split(' '))

def __main__():
    me = Person('Foo')
    me.name = me.set_name('foo bar')_

私が呼び出したそれらの行を参照してください?彼らは彼らがしていると言うことをしていませんまったく

Setという名前の関数は設定されていません。そのフォーマットは、設定されていません。

もちろん、_format_name_を書くのは悪い方法ではありません。そしてそれは関数です-それは何かを返すからです。そして、(私のJavaを許して)public void setName(String name)void戻り値型を持っています-誰にとっても役に立たないためです。それはその意図を明確に伝えています-「私はこの値を設定し、それを行います。心配する必要はありません。」

メソッドは有用なの戻り値のみを持つ必要があります。

9
user40980

me.name = me.set_name( 'foo bar')

これを行わないでください。

何がより良い実践か知りたい場合は、例を読んでください。最初:

  1. Fooという名前の人を作る
  2. その人の名前を「foo bar」に設定します

今度は2つ目:

  1. Fooという名前の人を作る
  2. 人物の名前を「foo bar」に設定するメソッドに人物の名前を設定します
    • ここでメソッドの一部として「セット」を使用することは本当に悪い考えです。なぜなら、「フォーマット名」がより多くなるため、例を処理することが不可能になるからです。

これらすべての要点は、ここでの問題は「メソッドが戻るのか」ではないということです。質問ではなく、それは「あなたがしていることをしますまったく意味がある」についてです。関数が何も返さない場合、void/no return関数は完全に意味があります。

そのため、set_nameメソッドに別の名前を付ける必要があるため、意味があるか、それを取り除く必要があります。現時点で問題があるのは、set_nameが非常に貧弱な関数であるためです。

あなたの例は次のいずれかである場合より良いでしょう:

  • __init__は設定時に名前をフォーマットしました
  • 名前をコンストラクタに渡すことを許可しておらず、現在のset_nameが実際にフォーマットされた名前を設定した

次のような例:

class Person:
    def __init__(self, name):
        self._name = self._format_name(name)

    def _format_name(self, n):
        return ' '.join(s[0].upper() + s[1:] for s in n.split(' '))

def __main__():
    me = Person('Foo')

理にかなっています。名前の設定を許可してから、誰かに具体的に名前をフォーマットするように要求する理由を質問します。あなたも行うかもしれません:

class Person:
    def __init__(self):
        pass

    def set_name(self, n):
        self._name = ' '.join(s[0].upper() + s[1:] for s in n.split(' '))

def __main__():
    me = Person()
    me.set_name('foo')
1
enderland

方法2は忌まわしいです。ユーザーに実際の目的ではない追加の作業を行わせています。他の人が指摘したように、あなたは名前を設定していません。あなたはそれをフォーマットしています。少なくとも、クラスメソッドである必要があります。

class Person:
    def __init__(self, name):
        self.name = name

    @classmethod
    def format_name(new_name):
        return ' '.join(s[0].upper() + s[1:] for s in new_name.split(' '))

def __main__():
    me = Person('Foo')
    me.name = me.format_name('foo bar')

しかし、それでも、ここでの問題は.nameは完全にパブリックプロパティです。ユーザーに名前のフォーマットについて知ってもらう必要があるのはなぜですか。

ゲッター/セッターの場合、値を返さないことが醜いという問題がある場合は、selfを返すことを検討してください。これにより、連鎖が可能になります。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def set_name(self, new_name):
        self.name = ' '.join(s[0].upper() + s[1:] for s in new_name.split(' '))

        return self

    def set_age(self, age):
        self.age = age

        return self

def __main__():
    me = Person('Foo', 50)
    me.set_name('foo bar').set_age(42)
0
Gort the Robot