将成员变量作为方法参数传递


33

在一个项目中,我发现了这样的代码:

class SomeClass
{
    private SomeType _someField;

    public SomeType SomeField
    {
        get { return _someField; }
        set { _someField = value; }
    }

    protected virtual void SomeMethod(/*...., */SomeType someVar)
    {
    }

    private void SomeAnotherMethod()
    {
        //.............
        SomeMethod(_someField);
        //.............
    }

};

我如何说服我的队友这是错误的代码?

我相信这是不必要的并发症。如果已经有成员变量,为什么还要将其作为方法参数传递呢?这也违反了封装。

您还发现此代码还有其他问题吗?


21
是什么让您认为这很不好?
yannis 2012年

@Yannis Rizos,你觉得很好吗?至少这是不必要的并发症。如果已经可以访问变量,为什么还要将其作为方法参数传递呢?这也违反了封装。
tika 2012年

2
有效点,请编辑问题以将其包括在内。我们无法帮助您说服队友,但是我们可以帮助您评估代码,这就是您的问题所在。
yannis

8
我宁愿有一个方法可以总结出不同的变量,而不是一个有2 + 2常数的方法。
但丁2012年

我认为重要的一点是该参数的类型。如果它是引用类型,我认为没有优势,但是如果它是值类型,我认为它是有道理的,因为如果您修改该变量类型,编译器会警告您有关代码被破坏的地方。
雷米

Answers:


3

我认为这是一个有效的话题,但是得到不同答案的原因是因为问题的形成方式。就我个人而言,我在团队中有相同的经历,在这种情况下,不需要通过成员作为参数,并且使代码复杂。我们有一个可以与一组成员一起使用的类,但是某些函数直接访问成员,而其他函数会通过参数来修改相同的成员(即使用完全不同的名称),因此绝对没有技术上的理由。出于技术原因,我指的是凯特提供的示例。

我建议退后一步,而不是完全专注于作为参数传递成员,而是与您的团队就清晰性和可读性展开讨论。要么更正式地讨论,要么仅在走廊上讨论使某些代码段更易于阅读而使其他代码段更困难的原因。然后,确定您希望成为一个团队的质量度量或干净代码的属性。毕竟,即使是在绿色领域的项目上工作,我们也要花费90%以上的时间来阅读,并且一旦编写代码(比如说10到15分钟后),它就会进入维护状态,在此情况下,可读性就显得尤为重要。

因此,对于您在此处的特定示例,我将使用的论据是:与编写更多代码相比,编写更少的代码总是更容易阅读。具有3个参数的函数比没有或具有1个参数的函数更难于大脑处理。如果还有另一个变量名,则在读取代码时,大脑必须跟踪另一件事。因此,要记住“ int m_value”然后是“ int localValue”,并要记住一个真的意味着另一个对您的大脑而言总是比使用“ m_value”工作更昂贵。

如需更多弹药和想法,我建议您拿一份鲍勃叔叔的《清洁守则》


在看到所引用的书的影响后,投票否决了。
Frank Hileman '17

尽管我的一小部分人对写答案的5年后感到非常难过,但是您只是从我身上夺走了2个互联网点,但我的另一半却很好奇,如果您能提供一些表明(可能很差)的参考资料我引用的书具有的影响力。这似乎是公平的,几乎值得这些要点
DXM

参考是我自己,亲自了解对其他开发人员的影响。这样的书背后的所有动机都是好的,但是,通过指定所有代码必须遵循非标准的指导原则(即实际上用于某个目的的指导原则),这样的书似乎导致批判性思维的丧失,有些人将其理解为类似于邪教的思想。 。
Frank Hileman '17

关于大脑处理挑战,请考虑认知负荷
jxramos

30

我可以想到在(专用)方法中将成员字段作为参数传递的理由:它明确说明了您的方法所依赖的内容。

就像您说的那样,所有成员字段都是方法的隐式参数,因为整个对象都是如此。但是,计算结果真的需要完整的对象吗?如果SomeMethod是仅依赖于的内部方法_someField,则使该依赖关系明确化是否更清洁?实际上,明确显示此依赖关系还可能暗示您实际上可以从类中重构这段代码!(请注意,我假设我们在这里不是在讨论getter或setter,而是实际计算出内容的代码)

我不会为公共方法提供相同的参数,因为调用者既不知道也不关心对象的哪一部分与计算结果有关...


2
剩下的隐式依赖关系是什么?我从您的示例中假设这_someField是计算结果所需的唯一参数,而我们只是明确了它。(注意:这很重要。我们没有添加依赖项,只是明确了它!)
Andres F.

11
-1如果没有对实例成员的隐式依赖,则它应该是一个以值作为参数的静态成员。在这种情况下,尽管您提出了一个理由,但我认为这不是一个正当的理由。这是错误的代码。
史蒂文·埃弗斯

2
@SnOrfus我实际上建议完全在类之外重构该方法
Andres F.

5
+1。“ ...使这种依赖关系显露起来更干净吗?” 敬畏地吓坏了。谁会说“全局变量一般来说是好的”?COBOL-68就是这样。现在听我说,以后再相信我。在我们的非平凡代码中,我有时会重构以明确传达使用类全局变量的位置。在许多情况下,我们已经通过以下方式解决了问题:a)私有字段及其后的私有属性的后n次使用b)通过“隐藏依赖项”来混淆字段的转换。现在,将其乘以3-5个深层次的继承链。
radarbob

2
@Tarion我不同意Bob叔叔。方法应尽可能类似于函数,并且仅依赖于显式依赖项。(在OOP中调用公共方法时,这些依赖项之一是this(或self),但它是由调用本身显式表示的obj.method(x)。)其他隐式依赖关系是对象的状态。这通常会使代码难以理解。只要有可能-并在合理的范围内-使依赖关系显式化和功能化。对于私有方法,如果可能,请显式传递其所需的每个参数。是的,这有助于将它们重构。
Andres F.

28

我看到将成员变量作为函数参数传递给私有方法的一个重要原因-函数纯度。从成员的角度来看,成员变量实际上是一个全局状态,而且,如果该成员在方法执行期间发生更改,则该变量是可变的全局状态。通过用方法参数替换成员变量引用,我们可以有效地使函数纯。纯函数不依赖于外部状态,也没有副作用,在给定相同输入参数集的情况下始终返回相同的结果-从而使测试和将来的维护更加容易。

确保将所有方法都用OOP语言作为纯方法既不容易也不实际。但是我相信,通过使处理纯复杂逻辑的方法和使用不可变变量,同时将不纯的“全局”状态处理保持在专用方法中的分离,可以在代码清晰度方面获得很多好处。

我认为,在外部调用函数时将成员变量传递给同一对象的公共函数将构成主要的代码味道。


5
Supurb回答。尽管非常理想,但今天的软件中的许多类都比创建“ Globals are Evil”阶段时的整个程序更大,更丑陋。实际上,类变量是类实例范围内的全局变量。在纯函数中完成大量工作会使代码更具可测试性和鲁棒性。
mattnz

@mattnz有什么办法可以提供到Hwat参考书目或他有关理想编程的任何书籍的链接?我一直在搜寻互联网,在他身上找不到任何东西。Google一直试图将其自动更正为“ what”。
Buttle Butkus

8

如果函数被多次调用,有时会传入该成员变量,有时还会传递其他内容,那么就可以了。例如,我完全不会认为这很糟糕:

if ( CalculateCharges(newStartDate) > CalculateCharges(m_StartDate) )
{
     //handle increase in charges
}

其中newStartDate是一些局部变量,m_StartDate是成员变量。

但是,如果仅通过传递给成员变量的函数来调用该函数,那很奇怪。成员函数始终对成员变量起作用。他们可能正在这样做(取决于您使用的语言)来获取成员变量的副本-如果是这种情况,并且您看不到,那么如果将整个过程明确化,代码可能会更好。


3
没关系,该方法称为具有比成员变量的其他参数。重要的是可以这样称呼它。您并不总是知道创建方法时最终将如何调用该方法。
Caleb

也许您最好用“ needHandleIncreaseChages(newStartDate)”替换“ if”条件,然后您的参数不再成立。
Tarion

2

没有人碰到的是SomeMethod是虚拟受保护的。意味着派生类可以使用它并重新实现其功能。派生类将无法访问私有变量,因此无法提供依赖于私有变量的SomeMethod的自定义实现。声明不要求依赖于私有变量,而是要求调用者将其传递给它。


您缺少的是此私有成员变量具有公共访问器。
tika 2012年

因此,您是说受保护的虚拟方法应该依赖于私有变量的公共访问器?您对当前代码有疑问吗?
迈克尔·布朗

创建公共访问器以供使用。期。
tika 2012年

1

GUI框架通常具有某种“ View”类,该类代表在屏幕上绘制的内容,并且该类通常提供一种方法,例如invalidateRect(Rect r)将其绘制区域的一部分标记为需要重绘。客户端可以调用该方法来请求更新部分视图。但是视图也可以调用自己的方法,例如:

invalidateRect(m_frame);

重新绘制整个区域。例如,当它第一次添加到视图层次结构时,它可能会这样做。

这样做没有错-视图的框架是有效的矩形,并且视图本身知道它想重绘。View类可以提供一个单独的方法,该方法不带参数,而使用视图的框架:

invalidateFrame();

但是,当可以使用更通用的方法时,为什么还要为此添加特殊方法invalidateRect()呢?或者,如果您确实选择提供invalidateFrame(),则很可能会从更笼统的角度来实现它invalidateRect()

View::invalidateFrame(void)
{
    invalidateRect(m_frame)
}

如果已经可以访问变量,为什么还要将其作为方法参数传递呢?

应该通过实例变量作为参数传递给自己的方法,如果该方法不会对实例变量具体操作。在上面的示例中,就该invalidateRect()方法而言,视图的框架只是另一个矩形。


1

如果该方法是实用程序方法,则这是有意义的。假设您需要从几个自由文本字符串中获得一个唯一的短名称。

您不希望为每个字符串编写单独的实现,而是将字符串传递给通用方法很有意义。

但是,如果该方法始终对单个成员起作用,那么将其作为参数传递似乎有点愚蠢。


1

在类中具有成员变量的主要原因是允许您将其设置在一个位置,并使它的值可用于该类中的所有其他方法。因此,总的来说,您希望不需要将成员变量传递给类的方法。

但是我可以想到几个原因,您可能希望将成员变量传递给该类的另一个方法。第一个是,如果您需要保证在与被调用方法一起使用时,成员变量的值需要保持不变,即使该方法需要在过程中的某个时刻更改实际成员变量的值。第二个原因与第一个原因有关,例如,当实现流利的语法时,您可能希望保证方法链范围内值的不变性。

考虑到所有这些,如果您将成员变量传递给其类的方法之一,我就不会说代码是“错误的”。但是,我建议这样做通常并不理想,因为它可能会鼓励很多代码重复,例如,将参数分配给局部变量,而多余的参数会在不需要的代码中添加“噪音”。如果您是“清洁代码”书的忠实拥护者,您会知道它提到应该将方法参数的数量保持在最低水平,并且前提是该方法没有其他更明智的方法来访问参数。


1

考虑这一点时,我想到了一些事情:

  1. 通常,带有较少参数的方法签名更易于理解。发明方法的一个重要原因是通过将长参数列表与它们所操作的数据相结合来消除长参数列表。
  2. 使方法签名依赖于成员变量将使将来更改这些变量变得更加困难,因为您不仅需要在方法内部进行更改,而且还需要在方法被调用的任何地方进行更改。并且由于示例中的SomeMethod受保护,因此子类也将需要更改。
  3. 不依赖于类内部的方法(公共或私有)不必位于该类上。可以将它们分解为实用程序方法,并且同样会感到高兴。他们几乎没有业务属于该阶层。如果在移动方法后没有其他方法依赖于该变量,则该变量也应该使用!最有可能的是,变量应该在其自己的对象上,并且对该方法进行操作的一个或多个方法变为公共并由父类组成。
  4. 将数据传递给诸如类之类的各种函数是一个过程程序,它具有全局变量,只是面对OO设计而已。这不是成员变量和成员函数的意图(请参见上文),听起来好像您的类不是很紧密。我认为这是一种代码味道,它暗示了一种更好的方式来对数据和方法进行分组。

0

如果参数(“ argument”)是引用或只读的,则会丢失您。

class SomeClass
{
    protected SomeType _someField;
    public SomeType SomeField
    {
        get { return _someField; }

        set {
          if (doSomeValidation(value))
          {
            _someField = value;
          }
        }
    }

    protected virtual void ModifyMethod(/*...., */ ref SomeType someVar)
    { 
      // ...
    }    

    protected virtual void ReadMethod(/*...., */ SomeType someVar)
    { 
      // ...
    }

    private void SomeAnotherMethod()
    {
        //.............

        // not recommended, but, may be required in some cases
        ModifyMethod(ref this._someField);

        //.............

        // recommended, but, verbose
        SomeType SomeVar = this.someField;
        ModifyMethod(ref SomeVar);
        this.someField = SomeVar;

        //.............

        ReadMethod(this.someField);
        //.............
    }

};

许多开发人员通常在构造函数方法中直接分配变量的内部字段。有一些例外。

请记住,“设置者”可以具有其他方法,而不仅仅是分配,有时甚至可以是虚拟方法或调用虚拟方法。

注意:建议将属性的内部字段保持为“受保护”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.