메소드 변수로 멤버 변수 전달


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

@ Yannis Rizos, 당신은 좋은 생각하십니까? 최소한 이것은 불필요한 합병증입니다. 이미 변수에 액세스 할 수있는 경우 변수를 메소드 매개 변수로 전달하는 이유는 무엇입니까? 캡슐화 위반이기도합니다.
tika

2
유효한 포인트, 포함하도록 질문을 편집하십시오. 팀 동료를 설득하는 데 도움을 줄 수는 없지만 코드를 평가하는 데 도움을 줄 수 있습니다. 이것이 바로 귀하의 질문입니다.
yannis

8
오히려 상수 2 + 2를 수행하는 방법과 다른 변수를 요약 할 수있는 방법을 원합니다.이 방법의 매개 변수는 재사용 성을위한 것입니다.
단테

여기서 중요한 점은 해당 매개 변수의 유형입니다. 그것이 참조 유형이라면 이점이 없지만 가치 유형이라면 변수 유형을 수정하면 컴파일러가 코드를 파산 한 장소에 대해 경고 할 것이기 때문에 그것이 sence라고 생각합니다.
Rémi

답변:


3

나는 이것이 유효한 주제라고 생각하지만, 당신이 혼합 된 답변을 얻는 이유는 질문이 형성 된 방식 때문입니다. 개인적으로, 나는 팀원들과 같은 경험을 가졌으며 멤버로 인수를 전달하는 것이 불필요하고 코드를 복잡하게 만들었습니다. 우리는 일련의 멤버와 함께 작동하는 클래스를 가지고 있지만 일부 함수는 멤버에 직접 액세스하고 다른 함수는 매개 변수를 통해 동일한 멤버를 수정합니다 (예 : 완전히 다른 이름 사용). 그렇게 할 기술적 인 이유는 전혀 없었습니다. 기술적 이유로, 나는 Kate가 제공 한 예를 의미합니다.

한 걸음 물러나서 매개 변수로 전달하는 멤버에 초점을 맞추는 대신 명확성과 가독성에 대해 팀과 토론을 시작하는 것이 좋습니다. 좀 더 공식적으로 또는 복도에서, 일부 코드 세그먼트를보다 쉽게 ​​읽고 다른 코드 세그먼트를 더 어렵게 만드는 것에 대해 논의하십시오. 그런 다음 노력하고 싶은 팀으로서 품질 코드 또는 깨끗한 코드의 속성을 식별하십시오. 결국, 그린 필드 프로젝트를 수행 할 때도 90 % 이상의 시간을 읽는 데 시간이 걸리고 코드가 작성 되 자마자 (10-15 분 후) 가독성이 더욱 중요한 유지 보수 작업에 사용됩니다.

따라서 귀하의 특정 예에서, 내가 사용할 주장은 더 많은 코드보다 적은 코드가 항상 읽기 쉽다는 것입니다. 매개 변수가 3 개있는 함수는 매개 변수가 없거나 1 개있는 함수보다 뇌가 처리하기가 어렵습니다. 다른 변수 이름이 있다면 뇌는 코드를 읽을 때 또 다른 것을 추적해야합니다. "int m_value"를 기억 한 다음 "int localValue"를 기억하고 하나는 실제로 다른 하나는 항상 뇌에 더 비싸다는 것을 의미하고 단순히 "m_value"로 작업한다는 것을 기억하십시오.

더 많은 탄약과 아이디어를 얻으려면 Bob Uncle Bob의 Clean Code 사본을 선택하는 것이 좋습니다 .


참조 된 책의 영향을 본 후 공감.
Frank Hileman

답변을 작성한 후 5 년이 지나서 저의 작은 부분이 매우 슬프지만, 당신은 저로부터 2 개의 인터넷 포인트를 가져 갔지만, 저의 작은 부분이 있습니다 (아마도 나쁜)를 나타내는 참조를 제공 할 수 있는지 궁금합니다. 내가 참조한 책에 영향을 미쳤습니다. 그것은 공평하고 거의 그 가치가있는 것처럼 보일 것입니다
DXM

참조는 나 자신이며 다른 개발자에게 미치는 영향을 개인적으로 봅니다. 그러나 그러한 책 뒤에 숨은 동기는 모두 훌륭하지만, 모든 코드가 비표준 지침 (즉, 실제로 목적을 제공하는 지침)을 따라야한다고 명시함으로써 그러한 책은 비판적 사고의 상실을 초래하는 것으로 보입니다. .
Frank Hileman 17시

두뇌 처리 문제와 관련하여, 인지 부하
jxramos

30

멤버 필드를 (비공개) 메소드의 매개 변수로 전달하는 정당화를 생각할 수 있습니다. 메소드가 의존하는 것을 명시 적으로 만듭니다.

당신이 말했듯이, 모든 멤버 필드는 전체 객체가 있기 때문에 메소드의 암시 적 매개 변수입니다. 그러나 결과를 계산하는 데 전체 개체가 실제로 필요합니까? 에 SomeMethod의존하는 내부 메소드 인 _someField경우이 종속성을 명시 적으로 만드는 것이 더 깨끗하지 않습니까? 실제로,이 의존성을 명시 적으로 만들면 클래스에서 실제로이 코드 조각을 리팩터링 할 수 있음을 암시 할 수도 있습니다! (참고로 여기서 우리는 getter 또는 setter에 대해 이야기하고 있지 않지만 실제로 무언가를 계산하는 코드에 대해 이야기한다고 가정합니다)

호출자는 결과의 계산과 관련하여 객체의 어느 부분을 알지 못하거나 신경 쓰지 않기 때문에 공개 메소드에 대해 동일한 주장을하지 않습니다 ...


2
나머지 암시 적 종속성은 무엇입니까? 귀하의 예 _someField에서 결과를 계산하는 데 필요한 유일한 매개 변수 라고 가정했으며 방금 명시 적으로 만들었습니다. (참고 : 이것은 중요합니다. 우리는 의존성을 추가 하지 않았 으며, 명시 적으로 만들었습니다!)
Andres F.

11
-1 인스턴스 멤버에 대한 암시 적 종속성이 없으면 값을 매개 변수로 사용하는 정적 멤버 여야합니다. 이 경우, 당신이 이유를 생각해 냈지만, 그것이 정당한 이유라고 생각하지 않습니다. 나쁜 코드입니다.
Steven Evers

2
@SnOrfus 나는 실제로 클래스에서 완전히 메소드 리팩토링을 제안했다
Andres F.

5
+1. "...이 의존성을 명백하게하는 것이 더 깨끗하지 않습니까?" 절대적으로 큰 소리로. 여기서 누가 "전역 변수가 일반적인 규칙으로 좋다"고 말할까요? 바로 COBOL-68입니다. 지금 내 말을 듣고 나중에 믿어주세요. 우리의 사소한 코드에서는 때때로 클래스 전역 변수가 사용되는 곳을 명시 적으로 전달하기 위해 리팩터링합니다. 우리는 많은 경우에 a) 개인 필드의 임의의 앞뒤 사용과 공공 재산 b) "종속성을 숨겨서"필드의 변환을 난독 화함으로써 멍청한 짓을 멈췄습니다. 이제 이것을 3-5 딥 상속 체인으로 곱하십시오.
radarbob

2
@Tarion 나는 Bob 삼촌과 동의하지 않습니다. 가능할 때마다 메소드는 기능과 유사해야하며 명시 적 종속성에만 의존해야합니다. OOP에서 공개 메소드를 호출 할 때 이러한 종속성 중 하나는 this(또는 self)이지만 호출 자체에 의해 명시됩니다 ( obj.method(x)). 다른 암시 적 종속성은 객체의 상태입니다. 이것은 일반적으로 코드를 이해하기 어렵게 만듭니다. 가능하고 이유가있을 때마다 종속성을 명시적이고 기능적인 스타일로 만듭니다. 개인 메소드의 경우 가능하면 필요한 모든 매개 변수를 명시 적으로 전달하십시오. 그렇습니다. 리팩터링하는 데 도움이됩니다.
Andres F.

28

멤버 변수를 개인 메서드에 함수 인수로 전달하는 강력한 이유 중 하나는 함수 순도입니다. 멤버 변수는 사실상 관점에서 볼 때 전역 상태이며, 또한 멤버가 메소드 실행 중에 변경되면 변경 가능한 전역 상태입니다. 멤버 변수 참조를 메서드 매개 변수로 바꾸면 함수를 효과적으로 순수하게 만들 수 있습니다. 순수한 기능은 외부 상태에 의존하지 않으며 부작용이 없으므로 동일한 입력 매개 변수 세트가 주어지면 항상 동일한 결과를 반환하므로 테스트 및 향후 유지 보수가 쉬워집니다.

모든 방법을 OOP 언어의 순수한 방법으로 사용하는 것은 쉽지도 실용적이지 않습니다. 그러나 복잡한 논리를 순수하게 처리하고 불변 변수를 사용하는 메소드를 사용하면서 불완전한 "전역"상태 처리를 전용 메소드 내에서 분리하여 유지함으로써 코드 명확성 측면에서 많은 이점을 얻을 수 있다고 생각합니다.

그러나 함수를 외부에서 호출 할 때 멤버 변수를 동일한 객체의 공용 함수에 전달하면 내 의견으로는 주요 코드 냄새가납니다.


5
서 퍼브 답변. 허트가 이상적 임에도 불구하고 오늘날 소프트웨어의 많은 클래스는 "Globals is Evil"단계가 만들어 졌을 때 전체 프로그램보다 더 크고 추악합니다. 클래스 변수는 실용적으로 클래스 인스턴스의 범위 내에서 전역 변수입니다. 순수한 기능으로 많은 양의 작업을 수행하면 훨씬 더 테스트 가능하고 강력한 코드를 만들 수 있습니다.
mattnz

@mattnz 당신이 Hwat의 서지 나 이상적인 프로그래밍에 관한 그의 책에 대한 링크를 제공 할 수있는 방법이 있습니까? 인터넷을 been이 뒤져서 아무것도 찾을 수 없습니다. Google은이를 "무엇"으로 자동 수정하려고 노력하고 있습니다.
Buttle Butkus

8

함수가 여러 번 호출되고 때로는 해당 멤버 변수를 전달하고 때로는 다른 것을 전달하면 괜찮습니다. 예를 들어, 나는 이것을 나쁜 것으로 간주하지 않을 것입니다 :

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

여기서 newStartDate로컬 변수이며 m_StartDate멤버 변수입니다.

그러나 멤버 변수가 전달 된 상태에서만 함수가 호출되면 이상합니다. 멤버 함수는 항상 멤버 변수에서 작동합니다. 멤버 변수의 사본을 얻기 위해 (작업중 인 언어에 따라)이 작업을 수행 할 수 있습니다.이 경우 전체 프로세스를 명시 적으로 만들면 코드가 더 좋을 수 있습니다.


3
멤버 변수 이외의 매개 변수 를 사용하여 메서드 호출 하는 것은 중요하지 않습니다 . 중요한 것은 그것이 그런 식 으로 불릴 수 있다는 것입니다. 메소드를 작성할 때 메소드가 어떻게 호출되는지 항상 알 수있는 것은 아닙니다.
Caleb

"if"조건을 "needHandleIncreaseChages (newStartDate)"로 바꾸는 것이 좋을 것입니다. 그러면 인수가 더 이상 유지되지 않습니다.
Tarion 2018

2

아무도 손대지 않은 것은 SomeMethod가 가상으로 보호된다는 것입니다. 파생 클래스가 클래스를 사용하고 기능을 다시 구현할 수 있음을 의미합니다. 파생 클래스는 개인 변수에 액세스 할 수 없으므로 개인 변수에 의존하는 SomeMethod의 사용자 정의 구현을 제공 할 수 없습니다. 개인 변수에 대한 종속성을 가져 오는 대신 선언시 호출자가 변수를 전달해야합니다.


누락 된 것은이 개인 멤버 변수에 공개 접근자가 있다는 것입니다.
tika

보호 된 가상 메소드가 개인 변수의 공개 접근 자에 의존해야한다고 말하고 있습니까? 그리고 현재 코드에 문제가 있습니까?
Michael Brown

공개 접근자가 사용되도록 작성되었습니다. 기간.
tika

1

GUI 프레임 워크에는 일반적으로 화면에 그려진 것을 나타내는 일종의 'View'클래스가 있으며 해당 클래스는 일반적으로 invalidateRect(Rect r)그리기 영역의 일부를 다시 그려야하는 것으로 표시 하는 것과 같은 방법을 제공합니다 . 클라이언트는 해당 메소드를 호출하여보기의 일부에 대한 업데이트를 요청할 수 있습니다. 그러나 뷰는 다음과 같은 자체 메서드를 호출 할 수도 있습니다.

invalidateRect(m_frame);

전체 영역을 다시 그립니다. 예를 들어, 뷰 계층 구조에 처음 추가 될 때이 작업을 수행 할 수 있습니다.

이 작업에는 아무런 문제가 없습니다.보기의 프레임은 유효한 사각형이며보기 자체는 다시 그리기를 원한다는 것을 알고 있습니다. View 클래스는 매개 변수를 사용하지 않고 뷰의 프레임을 대신 사용하는 별도의 메서드를 제공 할 수 있습니다.

invalidateFrame();

그러나 더 일반적인 것을 사용할 수있을 때 왜 이것을 위해 특별한 방법을 추가 invalidateRect()합니까? 또는 제공하기로 선택했다면 invalidateFrame()더 일반적인 측면에서 구현할 것입니다 invalidateRect().

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

이미 변수에 액세스 할 수있는 경우 변수를 메소드 매개 변수로 전달하는 이유는 무엇입니까?

당신은 해야 자신의 방법에 매개 변수로 인스턴스 변수를 전달 하는 경우 이 방법은 해당 인스턴스 변수에 특별히 작동하지 않습니다. 위의 예에서 뷰의 프레임은 invalidateRect()방법에 관한 한 다른 사각형 입니다.


1

이 방법이 유틸리티 방법 인 경우 이치에 맞습니다. 예를 들어 여러 자유 텍스트 문자열에서 고유 한 짧은 이름을 파생시켜야한다고 가정 해보십시오.

각 문자열에 대해 별도의 구현을 코딩하지 않고 일반적인 방법으로 문자열을 전달하는 것이 좋습니다.

그러나 메소드가 항상 단일 멤버에서 작동하는 경우 메소드로 매개 변수로 전달하는 것이 약간 바보처럼 보입니다.


1

클래스에 멤버 변수가있는 주된 이유는 한 곳에서 변수를 설정하고 클래스의 다른 모든 메서드에서 값을 사용할 수 있도록하기위한 것입니다. 따라서 일반적으로 멤버 변수를 클래스의 메서드에 전달할 필요가 없습니다.

그러나 멤버 변수를 클래스의 다른 메소드에 전달하려는 몇 가지 이유를 생각할 수 있습니다. 첫 번째는 호출 된 메소드와 함께 사용될 때 멤버 변수의 값이 변경되지 않은 상태로 사용되어야한다는 것을 보장해야하는 경우입니다. 해당 메소드가 프로세스의 특정 시점에서 실제 멤버 변수 값을 변경해야하는 경우에도 마찬가지입니다. 두 번째 이유는 유창한 구문을 구현할 때 메소드 체인의 범위 내에서 값의 불변성을 보장하고자한다는 점에서 첫 번째 이유와 관련이 있습니다.

이 모든 것을 염두에두고 클래스 변수 중 하나에 멤버 변수를 전달하면 코드가 "나쁘다"고 말하지 않을 것입니다. 그러나 매개 변수가 예를 들어 로컬 변수에 할당되고 추가 매개 변수가 필요하지 않은 코드에 "노이즈"를 추가하는 경우 많은 코드 복제를 권장 할 수 있으므로 일반적으로 이상적이지 않다고 제안합니다. Clean Code book의 팬이라면 메소드 매개 변수의 수를 최소한으로 유지해야하며 메소드가 매개 변수에 액세스하는 다른 합리적인 방법이없는 경우에만 언급해야합니다. .


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);
        //.............
    }

};

많은 개발자는 일반적으로 생성자 메서드에서 변수의 내부 필드를 직접 할당합니다. 몇 가지 예외가 있습니다.

"세터 (setter)"는 할당뿐만 아니라 추가 메소드를 가질 수 있으며 때로는 가상 메소드이거나 가상 메소드를 호출 할 수도 있습니다.

참고 : 속성의 내부 필드를 "보호"로 유지하는 것이 좋습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.