Java에서 더 나은 스타일 (인스턴스 변수와 반환 값)


32

나는 종종 내 클래스의 일부 메소드에서 공통 데이터를 사용해야 할 때 사용할 두 가지 방법 중 하나를 결정하기 위해 고심하고 있습니다. 더 나은 선택은 무엇입니까?

이 옵션에서 추가 변수를 선언 할 필요가없고 메소드 매개 변수를 정의하지 않아도되도록 인스턴스 변수를 작성할 수 있지만 해당 변수가 인스턴스화 / 수정되는 위치가 명확하지 않을 수 있습니다.

public class MyClass {
    private int var1;

    MyClass(){
        doSomething();
        doSomethingElse();
        doMoreStuff();
    }

    private void doSomething(){
        var1 = 2;
    }

    private void doSomethingElse(){
        int var2 = var1 + 1;
    }

    private void doMoreStuff(){
        int var3 = var1 - 1;
    }
}

아니면 지역 변수를 인스턴스화하고 인수로 전달합니까?

public class MyClass {  
    MyClass(){
        int var1 = doSomething();
        doSomethingElse(var1);
        doMoreStuff(var1);
    }

    private int doSomething(){
        int var = 2;
        return var;
    }

    private void doSomethingElse(int var){
        int var2 = var + 1;
    }

    private void doMoreStuff(int var){
        int var3 = var - 1;
    }
}

대답이 둘 다 맞다면, 어느 것이 더 자주 보이거나 사용됩니까? 또한 각 옵션에 대해 추가 장단점을 제공 할 수 있다면 매우 가치가 있습니다.



1
중간 변수를 인스턴스 변수에 넣는 것이 이러한 변수에 대한 스레드 간 경합 가능성으로 인해 동시성이 더 어려워 질 수 있다고 아무도 지적하지 않았습니다.
sdenham

답변:


107

아직 언급하지 않은 것이 놀랍습니다 ...

var1실제로 객체 상태 의 일부 인지 여부 따라 다릅니다 .

이 두 가지 접근 방식이 모두 정확하고 스타일에 문제가 있다고 가정합니다. 당신은 잘못.

이것은 적절하게 모델링하는 방법에 관한 것입니다.

마찬가지로 객체의 상태private변경 하는 인스턴스 메소드가 있습니다 . 그것이 당신의 방법이하고 있지 않다면이어야합니다 private static.


7
@mucaho : 객체를 직렬화하는 것과 같은 일을 할 때 저장되는 객체의 부분에서와 같이 지속 상태 에 관한 transient것이기 때문에 이와 관련이 없습니다 . 예를 들어 ArrayList의 백업 저장소는 ArrayList의 상태에 매우 중요하지만, ArrayList를 직렬화 할 때는 여유 공간이 아닌 실제 ArrayList 요소를 보유하는 백업 저장소의 일부만 저장하려고하기 때문에 끝에 추가 요소 추가를 위해 예약되어 있습니다. transienttransient
user2357112는 Monica

4
var1몇 가지 메소드에 필요하지만 상태의 일부가 아닌 응답에 추가하는 경우 MyClass시간이 걸리고 var1해당 메소드를 다른 클래스에 사용하여 사용할 수 있습니다 MyClass.
Mike Partridge

5
@CandiedOrange 여기서 올바른 모델링에 대해 이야기하고 있습니다. "객체 상태의 일부"라고 말하면 코드의 리터럴 조각에 대해서는 이야기하지 않습니다. 우리는 논의하고 개념 어떤 상태의 개념, 해야 개체의 상태가 제대로 개념을 모델링 할 수 있습니다. "유용한 수명"이 가장 큰 결정 요인이 될 것입니다.
jpmc26

4
@CandiedOrange Next와 Previous는 분명히 공개적인 방법입니다. var1의 값이 일련의 전용 메소드에 대해서만 관련이있는 경우 이는 오브젝트의 지속적 상태의 일부가 아니므로 인수로 전달되어야합니다.
Taemyr

2
@CandiedOrange 당신은 두 개의 의견 후, 당신이 무엇을 의미하는지 명확히했습니다. 나는 당신이 첫 번째 답변에서 쉽게 가질 수 있다고 말하고 있습니다. 또한 네, 그저 대상 이상의 것이 있다는 것을 잊었습니다. 때로는 개인 인스턴스 메소드에 목적이 있음에 동의합니다. 난 그냥 생각 나지 못했습니다. 편집 : 나는 당신이 사실, 나에게 답장을 한 첫 번째 사람이 아니라는 것을 깨달았습니다. 내 잘못이야. 나는 왜 하나의 대답이 무질서, 한 문장, 무응답인지에 대해 혼란 스러웠고, 다음 대답은 실제로 설명했습니다.
기금 모니카의 소송

17

나는 어느 것이 더 널리 퍼져 있는지 모르지만 항상 후자를 할 것입니다. 데이터 흐름과 수명을보다 명확하게 전달하며 클래스의 모든 인스턴스를 초기화하는 동안 관련 수명 만있는 필드로 부 풀리지 않습니다. 나는 전자가 혼란스럽고 코드 검토가 훨씬 어려워 진다고 생각합니다. 왜냐하면 모든 메소드가 수정 될 가능성을 고려해야하기 때문 var1입니다.


7

변수 의 범위 를 가능한 한 줄이고 합리적으로 줄여야합니다 . 방법뿐만 아니라 일반적으로.

귀하의 질문에 따르면 변수가 객체 상태의 일부인지 여부에 달려 있습니다. 그렇다면 해당 범위, 즉 전체 객체에서 사용하는 것이 좋습니다. 이 경우 첫 번째 옵션을 사용하십시오. 그렇지 않다면, 변수의 가시성을 줄이고 따라서 전체적인 복잡성을 줄이기 때문에 두 번째 옵션을 사용하십시오.


5

Java에서 더 나은 스타일 (인스턴스 변수와 반환 값)

또 다른 스타일이 있습니다-컨텍스트 / 상태를 사용하십시오.

public static class MyClass {
    // Hold my state from one call to the next.
    public static final class State {
        int var1;
    }

    MyClass() {
        State state = new State();
        doSomething(state);
        doSomethingElse(state);
        doMoreStuff(state);
    }

    private void doSomething(State state) {
        state.var1 = 2;
    }

    private void doSomethingElse(State state) {
        int var2 = state.var1 + 1;
    }

    private void doMoreStuff(State state) {
        int var3 = state.var1 - 1;
    }
}

이 방법에는 여러 가지 이점이 있습니다. 예를 들어 상태 개체는 개체와 독립적으로 변경 될 수 있으므로 미래를위한 더 많은 공간을 제공합니다.

이것은 통화 중 일부 세부 정보를 보존해야하는 분산 / 서버 시스템에서도 잘 작동하는 패턴입니다. state객체에 사용자 정보, 데이터베이스 연결 등을 저장할 수 있습니다 .


3

부작용에 관한 것입니다.

var1국가의 일부 인지 물으면 이 질문의 요점을 놓치게됩니다. 그래도 var1지속되어야 한다면 반드시 인스턴스 여야합니다. 지속성이 필요한지 여부에 관계없이 작동하도록 접근 할 수 있습니다.

부작용 접근

일부 인스턴스 변수는 호출 간 개인 메소드 간 통신에만 사용됩니다. 이런 종류의 인스턴스 변수는 존재하지 않을 때 리팩토링 될 수 있지만 반드시 그럴 필요는 없습니다. 때때로 상황이 더 명확 해집니다. 그러나 이것은 위험이 없습니다.

변수가 두 개의 다른 개인 범위에서 사용되므로 변수를 범위 밖으로 내 보냅니다. 배치하려는 범위에 필요하지 않기 때문입니다. 혼란 스러울 수 있습니다. "글로벌은 악하다!" 혼란스러운 수준. 이것은 작동 할 수 있지만 확장 성이 떨어집니다. 작은 곳에서만 작동합니다. 큰 물체가 없습니다. 더 이상 상속 체인이 없습니다. 요요 효과를 일으키지 마십시오 .

기능적 접근

이제는 var1아무 것도 유지하지 않아도 공중 전화 사이에 보존하려는 상태에 도달하기 전에 모든 과도 값에 대해 사용해야 할 경우가 있습니다. 즉 var1, 더 많은 기능적 방법을 사용하여 인스턴스를 설정할 수 있습니다 .

따라서 상태의 일부이든 아니든 여전히 두 가지 방법 중 하나를 사용할 수 있습니다.

이 예제에서 'var1'은 디버거 외에 아무것도 존재하지 않는 것으로 캡슐화됩니다. 당신이 고의로 편향하고 싶지 않기 때문에 고의로 그렇게 한 것 같습니다. 다행히도 나는 상관하지 않습니다.

부작용의 위험

즉, 귀하의 질문이 어디에서 왔는지 알고 있습니다. 나는 여러 가지 방법으로 여러 수준에서 인스턴스 변수를 변경하고 다람쥐를 따라 가려고 비참한 yo yo 'ing 상속을 받았다. 이것이 위험입니다.

이것은 더 기능적인 접근 방식으로 나를 안내하는 고통입니다. 메소드는 해당 종속성을 문서화하고 서명으로 출력 할 수 있습니다. 이것은 강력하고 명확한 접근법입니다. 또한 개인 메소드를 전달한 내용을 변경하여 클래스 내에서 더 재사용 할 수 있습니다.

부작용의 단점

또한 제한적입니다. 순수한 기능에는 부작용이 없습니다. 그것은 좋은 일이 될 수 있지만 객체 지향적이지는 않습니다. 객체 지향의 큰 부분은 메소드 외부의 컨텍스트를 참조하는 기능입니다. OOP의 강점은 여기저기서 전 세계를 유출시키지 않고하는 것입니다. 나는 글로벌의 유연성을 얻었지만 클래스에 멋지게 포함되어 있습니다. 원하는 경우 하나의 메소드를 호출하고 모든 인스턴스 변수를 한 번에 변경할 수 있습니다. 그렇게하면 적어도 방법에 이름을 지정하여 그 일이 발생했을 때 사람들이 놀라지 않도록해야합니다. 의견도 도움이 될 수 있습니다. 때때로 이러한 의견은 "사후 조건"으로 공식화됩니다.

기능적 개인 메소드의 단점

기능적 접근 방식은 일부 종속성을 명확하게합니다. 순수한 기능적 언어가 아니라면 숨겨진 의존성을 배제 할 수 없습니다. 메소드 서명 만 살펴보면 나머지 코드에서 부작용을 숨기지 않는다는 것을 알 수 없습니다. 당신은하지 않습니다.

사후 조건

귀하와 팀의 다른 모든 사람들이 부작용 (사전 / 사후 조건)을 의견에 확실하게 문서화하면 기능적 접근 방식의 이득은 훨씬 적습니다. 그래, 나도 알아

결론

개인적으로 필자는 가능한 경우 기능적 개인 메소드를 선호하지만 솔직히 말하면 사전 / 사후 조건부 부작용 주석이 오래되었거나 메소드가 잘못 호출되었을 때 컴파일러 오류를 일으키지 않기 때문입니다. 부작용의 유연성이 실제로 필요한 경우가 아니라면 문제가 해결 될 것입니다.


1
"일부 인스턴스 변수는 호출 간 개인 메소드 간 통신에만 사용됩니다." 코드 냄새입니다. 인스턴스 변수를 이런 식으로 사용하면 클래스가 너무 크다는 신호입니다. 해당 변수와 메소드를 새 클래스로 추출하십시오.
kevin cline

2
이것은 실제로 의미가 없습니다. OP 기능적 패러다임에 코드를 작성할 수는 있지만 (이것은 일반적으로 좋은 것입니다), 그것은 분명히 질문의 맥락이 아닙니다. 패러다임을 변경하여 객체의 상태를 저장하지 않아도된다고 OP에 알리는 것은 실제로 관련이 없습니다 ...
jpmc26

@kevincline OP의 예제에는 하나의 인스턴스 변수와 3 개의 메소드 만 있습니다. 기본적으로 이미 새로운 클래스로 추출되었습니다. 이 예제가 유용한 것은 아닙니다. 수업 규모는 개인 도우미 메서드가 서로 통신하는 방법과 관련이 없습니다.
MagicWindow

@ jpmc26 OP는 var1패러다임을 변경하여 상태 변수로 저장하는 것을 피할 수 있음을 이미 보여주었습니다 . 클래스 범위는 상태가 저장되는 곳만이 아닙니다. 또한 포함 범위입니다. 즉, 변수를 클래스 수준에 두는 동기는 두 가지가 있습니다. 당신은 그것을 둘러싼 범위에 대해서만 그것을 주장 할 수 있으며 국가는 악이 아니지만 나는 그것이 악과의 무역과도 같다고 말합니다 . 나는 이것을하는 코드를 유지해야했기 때문에 이것을 알고 있습니다. 일부는 잘 이루어졌다. 일부는 악몽이었다. 둘 사이의 선은 상태가 아닙니다. 가독성입니다.
MagicWindow

상태 규칙은 가독성을 향상시킵니다. 1) 작업의 중간 결과가 상태처럼 보이게하지 않습니다. 2) 메소드의 종속성을 숨 깁니다. 분석법의 arity가 높으면, 분석법이 복잡하지 않고 진정으로 해당 분석법의 복잡성을 나타내거나 현재 설계에 불필요한 복잡성이 있습니다.
sdenham

1

첫 번째 변종은 직관적이지 않고 잠재적으로 위험 해 보입니다 (어떤 이유에서든 누군가 개인 메소드를 공개하는 것을 상상하십시오).

클래스 생성시 변수를 인스턴스화하거나 인수로 전달하려고합니다. 후자는 기능적 관용구를 사용하고 포함하는 객체의 상태에 의존하지 않는 옵션을 제공합니다.


0

객체 상태와 두 번째 방법이 선호되는 시점에 대한 답변이 이미 있습니다. 첫 번째 패턴에 대한 일반적인 사용 사례를 하나 추가하고 싶습니다.

첫 번째 패턴은 클래스가하는 모든 작업 이 알고리즘을 캡슐화하는 것이라면 완벽하게 유효 합니다 . 이를위한 한 가지 유스 케이스는 알고리즘을 단일 메소드에 작성하면 너무 클 수 있다는 것입니다. 따라서 작은 메소드로 분류하여 클래스를 만들고 하위 메소드를 비공개로 만듭니다.

이제 매개 변수를 통해 알고리즘의 모든 상태를 전달하면 지루할 수 있으므로 개인 필드를 사용하십시오. 또한 기본적으로 인스턴스 상태이기 때문에 첫 번째 단락의 규칙과 일치합니다. 이를 위해 개인 필드를 사용하는 경우 알고리즘이 재진입되지 않는다는 점을 명심하고 올바르게 문서화 하면 됩니다. 이것은 대부분의 경우 문제가되지 않지만 물릴 수 있습니다.


0

무언가를하는 예를 들어 보자. 이것이 자바가 아닌 자바 스크립트이므로 용서하십시오. 요점은 같아야합니다.

https://blockly-games.appspot.com/pond-duck?lang=en을 방문 하여 자바 스크립트 탭을 클릭 한 후 붙여 넣으십시오.

hunt = lock(-90,1), 
heading = -135;

while(true) {
  hunt()  
  heading += 2
  swim(heading,30)
}

function lock(direction, width) {
  var dir = direction
  var wid = width
  var dis = 10000

  //hunt
  return function() {
    //randomize() //Calling this here makes the state of dir meaningless
    scanLock()
    adjustWid()
    if (isSpotted()) {
      if (inRange()) {
        if (wid <= 4) {
          cannon(dir, dis)
        }
      }
    } else {
      if (!left()) {
        right()
      }
    }
  }

  function scanLock() {
    dis = scan(dir, wid);
  }

  function adjustWid() {
    if (inRange()) {
      if (wid > 1)
        wid /= 2;
    } else {
      if (wid < 16) {
        wid *= 2; 
      }
    }
  }

  function isSpotted() {
    return dis < 1000;
  }

  function left() {
    dir += wid;
    scanLock();
    return isSpotted();
  }

  function right() {
    dir -= wid*2;
    scanLock();
    return isSpotted()
  }

  function inRange() {
    return dis < 70;
  }

  function randomize() {
    dir = Math.random() * 360
  }
}

당신은 그 통지해야한다 dir, wid그리고 dis많은 주위에 전달되지 않아요. 또한 lock()반환 하는 함수의 코드는 의사 코드와 매우 비슷합니다. 실제 코드입니다. 그러나 읽기는 매우 쉽습니다. 우리는 전달과 할당을 추가 할 수 있지만 의사 코드에서는 볼 수없는 혼란을 더할 수 있습니다.

할당 및 전달을 수행하지 않는 것이 좋다고 주장하려면 세 가지 변수가 영구적 상태이기 때문에 dir루프마다 임의의 값 을 할당 하는 재 설계를 고려하십시오 . 지금은 영속적이지 않습니까?

물론, 이제 dir범위 내에서 떨어질 수는 있지만 통과와 설정으로 코드와 같은 의사를 어지럽히 지 않아도됩니다.

따라서 아닙니다. 상태는 통과 및 반환보다는 부작용을 사용하거나 사용하지 않기로 결정한 이유가 아닙니다. 부작용만으로도 코드를 읽을 수 없다는 의미는 아닙니다. 순수한 기능 코드이점을 얻지 못합니다 . 그러나 좋은 이름으로 잘하면 실제로 읽을 수 있습니다.

그들이 악몽처럼 스파게티로 변할 수는 없습니다. 그러나 무엇을 할 수 없습니까?

행복한 오리 사냥.


1
내부 / 외부 기능은 OP가 설명하는 것과 다른 패러다임입니다. -내부 함수에 전달 된 외부 함수 콘트라 인수에서 로컬 변수 간의 트레이드 오프는 인수가 개인 함수로 전달되는 클래스 콘트라스트의 개인 변수와 유사하다는 데 동의합니다. 그러나이 비교를 수행하면 객체의 수명이 단일 함수 실행에 해당하므로 외부 함수의 변수는 지속적 상태의 일부입니다.
Taemyr

@Taemyr OP는 public 생성자 내에서 호출 된 private 메소드를 설명합니다. 함수를 입력 할 때마다 상태를 밟으면 상태가 실제로 '지속적'이 아닙니다. 때로 상태는 공유 장소 메소드로 쓰고 읽을 수 있습니다. 그것이 지속되어야한다는 것을 의미하지는 않습니다. 어쨌든 지속된다는 사실은 의존해야 할 것이 아닙니다.
candied_orange

1
물체를 처리 할 때마다 밟아도 지속됩니다.
Taemyr

1
좋은 점이지만 JavaScript로 표현하면 토론을 모호하게 만듭니다. 또한 JS 구문을 제거하면 컨텍스트 객체가 전달됩니다 (외부 함수의 닫힘)
fdreger

1
@ sdenham 클래스의 속성과 작업의 일시적 중간 결과 사이에 차이가 있다고 주장합니다. 그것들은 동등하지 않습니다. 그러나 하나는 다른 하나에 저장 될 수 있습니다. 반드시 그럴 필요는 없습니다. 문제는 항상 그래야하는지입니다. 예, 의미 및 수명 문제가 있습니다. arity 문제도 있습니다. 당신은 그들이 충분히 중요하다고 생각하지 않습니다. 괜찮아. 그러나 객체 상태 이외의 것에 클래스 레벨을 사용하는 것은 잘못된 모델링이 한 가지 모델링 방법을 요구하고 있습니다. 다른 방법으로 수행 한 전문 코드를 유지 관리했습니다.
candied_orange
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.