게터의 논리


46

동료들은 게터와 세터에 가능한 한 적은 논리가 있어야한다고 말합니다.

그러나 나는 게터와 세터에 많은 것들이 숨겨져 사용자 / 프로그래머를 구현 세부 사항으로부터 보호 할 수 있다고 확신합니다.

내가하는 일의 예 :

public List<Stuff> getStuff()
{
   if (stuff == null || cacheInvalid())
   {
       stuff = getStuffFromDatabase();
   }
   return stuff;
}

일이 어떻게해야하는지에 대한 예 (Bob 아저씨의 '클린 코드'인용) :

public List<Stuff> getStuff()
{
    return stuff;
}

public void loadStuff()
{
    stuff = getStuffFromDatabase();
}

setter / getter에서 얼마나 많은 로직이 적절합니까? 데이터 숨기기 위반을 제외하고 빈 게터 및 세터를 사용하는 것은 무엇입니까?


6
이것은 나에게 tryGetStuff ()처럼 보인다 ...
Bill Michell

16
이것은 '게터'가 아닙니다. 이 용어는 실수로 이름에 'get'을 넣는 방법이 아니라 속성의 읽기 접근자를 위해 사용됩니다.
Boris Yankov

6
그 두 번째 예가 당신이 언급 한이 깨끗한 코드 북 의 공정한 예 인지 또는 누군가가 그것에 대해 막대기의 잘못된 끝을 얻는 지 모르겠지만 깨지기 쉬운 것은 깨끗한 코드입니다.
Jon Hanna

@BorisYankov 음 ... 두 번째 방법은. public List<Stuff> getStuff() { return stuff; }
R. Schmitz

정확한 사용 사례에 따라 캐싱을 별도의 클래스로 분리하고 싶습니다. StuffGetter인터페이스를 만들고 StuffComputer계산을 수행하는 을 구현 한 다음 객체에 래핑 StuffCacher하여 캐시에 액세스하거나 StuffComputer랩으로 래핑 하는 호출을 전달합니다 .
Alexander

답변:


71

일이 당신에게 일을하는 방식은 절름발이입니다.

일반적으로 내가하는 일은 다음과 같습니다. 만약 물건을 계산하는 것이 저렴하거나 캐시에서 발견 될 가능성이 높으면 getStuff () 스타일이 좋습니다. 물건을 얻는 것이 계산적으로 비싸고, 너무 비싸서 인터페이스에서 고가를 광고 해야하는 경우 getStuff ()라고 부르지 않을 것입니다. 해야 할 일이있을 것입니다.

두 경우 모두, loadStuff ()가 미리 호출되지 않은 경우 getStuff ()가 작동하지 않기 때문에 작업에서 작업을 지시하는 방식이 엉망입니다. 그것에. 작업 순서는 제가 생각할 수있는 최악의 복잡성에 관한 것입니다.


23
주문 순서 복잡성을 언급 한 +1 해결 방법으로, 작업에서 생성자에서 항상 loadStuff ()를 호출하도록 요청할 수 있지만 항상로드해야한다는 의미이기 때문에 나쁠 것입니다. 첫 번째 예에서는 필요할 때만 데이터를 느리게로드 할 수 있습니다.
laurent

6
나는 보통 "정말 싸면 속성 게터를 사용하십시오. 비싸면 함수를 사용하십시오"라는 규칙을 따릅니다. 그것은 일반적으로 나에게 도움이되며 강조하기 위해 지시 한대로 적절하게 명명하면 나에게도 좋아 보입니다.
Denis Troller

3
실패 할 수 있다면 게터가 아닙니다. 이 경우 DB 링크가 다운되면 어떻게됩니까?
Martin Beckett

6
+1, 몇 개의 잘못된 답변이 게시되었는지에 약간 충격을 받았습니다. Getter / Setter는 구현 세부 정보를 숨기기 위해 존재합니다. 그렇지 않으면 변수를 공개해야합니다.
Izkata

2
loadStuff()함수보다 먼저 함수를 호출 해야한다는 것도 잊지 말고 getStuff()클래스가 후드 아래에서 일어나고있는 것을 적절히 추상화하지 못한다는 것을 의미합니다.
rjzii

23

게터의 논리는 완벽합니다.

그러나 데이터베이스에서 데이터를 얻는 것은 "논리적"이상입니다. 많은 일이 잘못 될 수 있고 비 결정적인 방식으로 일련의 매우 비싼 작업 이 필요합니다 . 나는 게터에서 암묵적으로 주저합니다.

반면, 대부분의 ORM은 기본적으로 정확히 수행중인 컬렉션의 지연 로딩을 지원합니다.


18

'Clean Code'에 따르면 가능한 한 많이 다음과 같이 분할해야한다고 생각합니다.

public List<Stuff> getStuff() {
   if (hasStuff()) {
       return stuff;
   }
   loadStuff();
   return stuff;
}

private boolean hasStuff() {
    if (stuff == null) {
       return false;
    }
    if (cacheInvalid()) {
       return false;        
    }
    return true;
} 

private void loadStuff() {
    stuff = getStuffFromDatabase();
}

물론, 이것은 당신이 작성한 아름다운 형식이 누군가가 한 눈에 이해하는 코드의 일부로 옳은 일을한다는 점을 감안할 때 완전히 말도 안됩니다.

public List<Stuff> getStuff() {
   if (stuff == null || cacheInvalid()) {
       stuff = getStuffFromDatabase();
   }
   return stuff;
}

물건이 후드 아래에 들어가는 방법은 발신자의 두통이 아니어야하며, 특히 임의의 "적절한 순서"로 물건을 호출하는 것을 기억하는 것은 호출자의 두통이 아니어야합니다.


8
-1. 실제 두통은 호출자가 단순한 getter 호출로 인해 느린 데이터베이스 액세스가 발생하는 이유를 파악할 때 발생합니다.
Domenic

14
@Domenic : 데이터베이스 액세스는 어쨌든 수행되어야하며, 수행하지 않아도 다른 사람의 성능을 저장하지 않습니다. 이것이 필요한 경우 List<Stuff>얻을 수있는 방법은 한 가지뿐입니다.
DeadMG

4
@lukas : 고마워, 나는 '깨끗한'코드에서 사소한 코드 비트를 만들기 위해 한 줄을 더 길게하는 데 사용되는 모든 트릭을 기억하지 못했습니다.
Joonas Pulakka

2
로버트 마틴을 비방하고 있습니다. 그는 간단한 부울 분리를 9 줄 함수로 확장하지 않을 것입니다. 함수 hasStuff는 깨끗한 코드와 반대입니다.
케빈 클라인

2
나는이 대답의 시작 부분을 읽었으며, "다른 책 숭배자들이 간다"고 생각하고 "물론 이건 말도 안된다"는 부분이 내 눈을 사로 잡았다. 잘했다! C-: =
Mike Nakis

8

그들은 게터와 세터에 가능한 한 적은 논리가 있어야한다고 말합니다.

클래스의 요구를 충족시키기 위해 필요한만큼의 논리가 필요합니다. 제 개인적 선호는 가능한 한 적은 것이지만 코드를 유지 관리 할 때는 일반적으로 기존 게터 / 세터와 원래 인터페이스를 그대로두고 새로운 비즈니스 논리를 수정하기 위해 많은 논리를 넣어야합니다 (예 : "고객" "911 이후 환경의 게터는"고객 파악 "및 OFAC 규정 을 충족해야 하며 회사 정책과 함께 특정 국가의 고객 (예 : 쿠바 또는이란)이 나타나지 않도록 금지해야합니다.

귀하의 예에서, "uncle bob"버전은 사용자 / 유지 업체가 전화 loadStuff()하기 전에 전화 해야한다는 것을 요구하므로 "uncle bob"샘플을 선호 getStuff()하지 않습니다. 이는 관리자 중 한 명이 잊어 버리면 재난을위한 레시피입니다. 더 나쁜, 알지 못했습니다). 지난 10 년 동안 근무한 대부분의 장소는 여전히 10 년 이상 된 코드를 사용하고 있으므로 유지 관리의 용이성을 고려해야합니다.


6

네 말이 맞아 동료가 틀렸어

get 메소드가해야하거나하지 말아야 할 것에 대한 모든 사람의 경험 법칙을 잊어 버리십시오. 클래스는 무언가를 추상화해야합니다. 수업을 읽을 수 stuff있습니다. Java에서는 'get'메소드를 사용하여 특성을 읽는 것이 일반적입니다. stuff를 호출 하여 읽을 것으로 예상되는 수십 줄의 프레임 워크가 작성되었습니다 getStuff. 함수 fetchStuff또는의 이름을 다른 이름으로 지정하면 getStuff클래스가 해당 프레임 워크와 호환되지 않습니다.

'getStuff ()'가 매우 복잡한 작업을 수행하고 실패시 RuntimeException을 발생시키는 Hibernate를 가리킬 수 있습니다.


최대 절전 모드는 ORM이므로 패키지 자체가 의도를 나타냅니다. 패키지 자체가 ORM이 아닌 경우 이러한 의도를 쉽게 이해할 수 없습니다.
FMJaguar

@FMJaguar : 완벽하게 쉽게 이해할 수 있습니다. 최대 절전 모드는 데이터베이스 작업을 추상화하여 개체 네트워크를 제공합니다. OP는 데이터베이스 작업을 추상화하여 이름이 속성 인 개체를 제공합니다 stuff. 둘 다 세부 사항을 숨겨서 호출 코드를 쉽게 작성할 수 있도록합니다.
케빈 클라인

해당 클래스가 ORM 클래스 인 경우, 다른 상황에서 의도가 이미 표현되어 있습니다. "다른 프로그래머가 getter 호출의 부작용을 어떻게 알 수 있습니까?" 이 프로그램은 1K 클래스와 -10 게터, 그 중 하나의 데이터베이스 호출 문제가 될 수 있도록하는 정책이 포함되어있는 경우
FMJaguar

4

이와 같은 소리는 함수 이름을 제어하는 ​​것을 선호하는 방법에 영향을받을 수있는 순전 한 응용 프로그램 토론 일 수 있습니다. 적용 된 관점에서 나는 오히려 오히려 볼 것이다.

List<String> names = clientRoster.getNames();
List<String> emails = clientRoster.getEmails();

반대로 :

myObject.load();
List<String> names = clientRoster.getNames();
List<String> emails = clientRoster.getEmails();

또는 더 나쁜 :

myObject.loadNames();
List<String> names = clientRoster.getNames();
myOjbect.loadEmails();
List<String> emails = clientRoster.getEmails();

비슷한 코드를 모두 넘어 가야하기 때문에 다른 코드를 훨씬 더 중복하고 읽기 어렵게 만드는 경향이 있습니다. 또한 로더 함수 또는 이와 유사한 기능을 호출하면 작업중인 객체의 구현 세부 정보에서 더 이상 추상화되지 않기 때문에 OOP를 사용하는 목적도 완전히 벗어납니다. clientRoster객체 가있는 경우을 getNames호출 해야하는 것처럼 작동 방식 에 신경 쓰지 않아도됩니다 . 클라이언트의 이름 loadNamesgetNames알려주십시오 List<String>.

따라서 문제가 의미와 데이터를 얻는 함수의 최고의 이름에 관한 것 같습니다. 회사와 다른 사람들이 getand set접두사에 문제가 있다면 retrieveNames대신 함수를 호출하는 것은 어떻습니까? 그것은 무슨 일이 일어나고 있는지를 나타내지 만 get방법 이 예상 할 수있는 것처럼 작업이 즉시 이루어질 것이라고 암시하지는 않습니다 .

접근 자 메서드의 논리와 관련하여 변수와의 명목상 상호 작용만으로 순간적으로 암시되므로 일반적으로 최소로 유지하십시오. 그러나 이는 일반적으로 단순한 유형, 복잡한 데이터 유형에만 적용됩니다. 즉 List, 속성에 올바르게 캡슐화하기가 더 어려우며 일반적으로 엄격한 변경자 및 접근 자와 반대로 상호 작용하기 위해 다른 방법을 사용합니다.


2

getter를 호출하면 필드를 읽는 것과 동일한 동작이 나타납니다.

  • 값을 검색하는 것이 저렴해야합니다
  • setter로 값을 설정 한 다음 getter로 값을 읽으면 값이 같아야합니다
  • 가치를 얻는 것은 부작용이 없어야합니다
  • 예외를 던져서는 안됩니다

2
나는 이것에 완전히 동의하지 않습니다. 부작용이 없어야한다는 데 동의하지만 필드와 구별되는 방식으로 구현하는 것이 좋습니다. .Net BCL을 보면 InvalidOperationException이 getter를 볼 때 널리 사용됩니다. 또한 MikeNakis가 작업 순서에 대한 답변을 참조하십시오.
Max

마지막 점을 제외한 모든 점에 동의하십시오. 값을 얻으려면 설정되지 않았을 수도있는 다른 값이나 리소스에 따라 계산이나 다른 작업을 수행해야 할 수도 있습니다. 이 경우 게터가 어떤 종류의 예외를 던질 것으로 기대합니다.
TMN

1
@TMN : 최상의 시나리오에서 클래스는 getter 예외를 처리 할 수있는 작업을 실행할 필요가 없도록 구성해야 합니다. 예외를 던질 수있는 장소를 최소화하면 예상치 못한 놀라움이 줄어 듭니다.
hugomg

8
구체적인 예를 들어 두 번째 요점에 동의하지 않습니다 foo.setAngle(361); bar = foo.getAngle(). bar일 수 361있지만 1각도가 범위에 묶여 있으면 합법적으로 발생할 수도 있습니다 .
zzzzBov

1
-1. (1) 이 예에서는 싼 - 게으른 로딩 후. (2) 현재 예제에는 "setter"가 없지만 누군가가 하나만 추가하고 그냥 설정 stuff하면 getter 동일한 값 반환합니다. (3) 예제에 표시된 게으른 로딩은 "보이는"부작용을 일으키지 않습니다. (4) 이후에 "지연로드"를 도입 이후 논란의 여지가, 어쩌면 유효한 시점입니다 수있는 이전의 API 계약을 변경 - 그러나 사람은 결정을 내려야하는 그 계약보고있다.
Doc Brown

2

자체 값을 계산하기 위해 다른 속성 및 메서드를 호출하는 게터도 종속성을 의미합니다. 예를 들어, 속성을 스스로 계산할 수 있어야하고 다른 멤버를 설정해야하는 경우 모든 멤버를 반드시 설정하지 않아도되는 초기화 코드에서 속성에 액세스 할 경우 우발적 인 null 참조에 대해 걱정해야합니다.

즉, '게터 내의 속성 지원 필드가 아닌 다른 멤버에 액세스하지 마십시오'는 객체의 필수 상태에 대해 암시하는 내용에주의를 기울이는 것을 의미합니다. 이 속성에 액세스 할 수 있습니다.

그러나 두 가지 구체적인 예에서 하나를 다른 하나보다 선택하는 이유는 완전히 다릅니다. 게터는 첫 번째 액세스 (예 : Lazy Initialization)에서 초기화 됩니다. 두 번째 예는 일부 사전 시점, 예를 들어 명시 적 초기화에서 초기화 된 것으로 가정 합니다 .

정확히 초기화가 발생하면 중요하지 않을 수도 있습니다.

예를 들어, 사용자가 처음 액세스를 트리거 할 때 (예 : 사용자 오른쪽 클릭, 상황에 맞는 메뉴가 나타나면 사용자가 예기치 않게 딸꾹질하는 대신 지연이 예상되는로드 단계 중에 수행해야하는 속도가 매우 느릴 수 있음) 이미 마우스 오른쪽 버튼을 다시 클릭했습니다).

또한 캐시 된 속성 값에 영향을 줄 수있는 모든 것이 발생하는 경우가 분명합니다. 종속성이 변경되지 않고 나중에 예외가 발생하는지 확인하고있을 수도 있습니다. 이 상황에서 계산하는 데 비용이 많이 들지 않더라도 코드 실행을 더 복잡하고 정신적으로 따르기가 더 어려워지는 것을 피하기 위해 해당 시점에서 값을 캐시하는 것이 좋습니다.

즉, 게으른 초기화는 다른 많은 상황에서 의미가 있습니다. 따라서 규칙을 따르기 어려운 프로그래밍에서 자주 발생하는 것처럼 구체적인 코드로 이어집니다.


0

@MikeNakis가 말한대로 해보십시오. 방금 물건을 얻는다면 괜찮습니다 ... 다른 일을하면 다른 일을하는 새로운 기능을 만들어서 공개하십시오.

귀하의 재산 / 기능이 이름에 명시된 것만 수행한다면 합병증의 여지가 많지 않습니다. 응집력이 핵심 IMO


1
이것에 대해 조심하십시오. 내부 상태를 너무 많이 노출시킬 수 있습니다. 당신은 빈의 많은 바람 싶지 않다 loadFoo()거나 preloadDummyReferences()또는 createDefaultValuesForUninitializedFields()클래스의 초기 구현을 필요로해서 방법.
TMN

물론 ... 난 그냥 당신이 많은 문제가 안된다는 것을 이름 상태한다면 ...하지만 당신이 말하는 것은 절대적으로 사실이라고 말하고 있었다 ...
이반 Crojach Karačić

0

개인적으로, 나는 생성자의 매개 변수를 통해 Stuff의 요구 사항을 노출하고 어느 클래스가 물건을 인스턴스화하여 어디에서 왔는지 알아내는 작업을 수행 할 수있게합니다. stuff가 null이면 null을 반환해야합니다. OP의 원본과 같은 영리한 솔루션을 시도하지 않는 것이 좋습니다. 왜냐하면 구현 내부에서 버그를 숨길 수있는 쉬운 방법이기 때문에 문제가 발생했을 때 무엇이 ​​잘못 될 수 있는지 분명하지 않습니다.


0

더 중요한 문제가 있고 여기에 "적절성"만 있으면 결정을 내려야합니다 . 주로 큰 결정은 사람들이 캐시를 우회하도록 허용하길 원하는지 여부입니다.

  1. 먼저 코드를 재구성 할 수있는 방법이 있는지 확인하여 필요한 모든로드 호출 및 캐시 관리가 생성자 / 초기화 기에서 수행되도록하십시오. 이것이 가능하면 1 부에서 복잡한 게터의 안전을 사용하여 2 부에서 간단한 게터를 수행 할 수있는 클래스를 만들 수 있습니다. (상생 시나리오)

  2. 그러한 클래스를 작성할 수없는 경우, 상충 관계가 있는지와 소비자가 캐시 확인 코드를 건너 뛰도록 허용할지 결정해야합니다.

    1. 소비자가 캐시 검사를 건너 뛰지 않아야하고 성능 불이익을 신경 쓰지 않는 것이 중요하다면 게터 내부에 검사를 연결하여 소비자가 잘못된 일을 할 수 없도록하십시오.

    2. 캐시 검사를 건너 뛰는 것이 좋거나 게터에서 O (1) 성능을 보장하는 것이 매우 중요하다면 별도의 호출을 사용하십시오.

당신이 이미 언급했듯이, 나는 "깨끗한 코드", "모든 것을 작은 기능으로 쪼개는"철학의 큰 팬이 아닙니다. 어떤 순서로든 호출 할 수있는 많은 직교 함수가 있다면 그것들을 적은 비용으로 더 표현력이 향상됩니다. 그러나 함수에 순서 종속성이 있거나 특정 순서로만 유용하면 함수를 분할하면 잘못된 일을 할 수있는 방법의 수가 증가하지만 이점은 거의 없습니다.


-1, 생성자는 초기화하지 말고 구성해야합니다. 데이터베이스 논리를 생성자에 넣으면 해당 클래스를 완전히 테스트 할 수 없으며,이 중 몇 개 이상이 있으면 응용 프로그램 시작 시간이 엄청납니다. 그리고 그것은 초보자를위한 것입니다.
Domenic

@Domenic : 이것은 시맨틱하고 언어 의존적 인 문제입니다. 객체가 사용하기에 적합하고 객체가 완전히 구축 된 이후에 그리고 이후에만 적절한 불변을 제공하는 지점.
hugomg

0

제 생각에 Getters에는 많은 논리가 없어야합니다. 부작용이 없어야하며 절대 예외가 없어야합니다. 물론, 당신은 무엇을하고 있는지 알고 있습니다. 내 게터의 대부분은 그들에 논리가 없으며 그냥 현장으로갑니다. 그러나 주목할만한 예외는 가능한 한 간단 해야하는 퍼블릭 API였습니다. 그래서 다른 getter가 호출되지 않으면 실패 할 getter가 하나 있습니다. 해결책? var throwaway=MyGetter;그것에 의존하는 게터 와 같은 코드 라인 . 나는 그것을 자랑스럽게 생각하지 않지만 여전히 더 깨끗한 방법을 보지 못합니다.


0

지연로드가있는 캐시에서 읽은 것처럼 보입니다. 다른 사람들이 지적했듯이 검사 및 로딩은 다른 방법에 속할 수 있습니다. 20 개의 스레드가 동시에로드되지 않도록로드를 동기화해야 할 수도 있습니다.

getCachedStuff()실행 시간이 일관되지 않으므로 getter 의 이름을 사용하는 것이 좋습니다.

cacheInvalid()루틴의 작동 방식에 따라 널 검사가 필요하지 않을 수 있습니다. stuff데이터베이스에서 채워 지지 않은 한 캐시가 유효 할 것으로 기대하지 않습니다 .


0

getter에서 목록을 반환하는 주요 논리는 목록을 수정할 수 없도록하는 논리입니다. 그것은 두 가지 예 모두 캡슐화를 깨뜨릴 수 있습니다.

같은 :

public List<Stuff> getStuff()
{
    return Collections.unmodifiableList(stuff);
}

게터에서의 캐싱에 관해서는 이것이 정상이라고 생각하지만 캐시를 만드는 데 상당한 시간이 걸리면 캐시 논리를 옮기고 싶은 유혹을받을 수 있습니다. 즉 그것은 달려있다.


0

정확한 사용 사례에 따라 캐싱을 별도의 클래스로 분리하고 싶습니다. StuffGetter인터페이스를 만들고 StuffComputer계산을 수행하는 을 구현 한 다음 객체에 래핑 StuffCacher하여 캐시에 액세스하거나 StuffComputer랩으로 래핑 하는 호출을 전달합니다 .

interface StuffGetter {
     public List<Stuff> getStuff();
}

class StuffComputer implements StuffGetter {
     public List<Stuff> getStuff() {
         getStuffFromDatabase()
     }
}

class StuffCacher implements StuffGetter {
     private stuffComputer; // DI this
     private Cache<List<Stuff>> cache = new Cache<>();

     public List<Stuff> getStuff() {
         if cache.hasStuff() {
             return cache.getStuff();
         }

         List<Stuffs> stuffs = stuffComputer.getStuff();
         cache.store(stuffs);
         return stuffs;
     }
}

이 디자인을 사용하면 캐싱을 쉽게 추가하고, 캐싱을 제거하고, 기본 파생 논리 (예 : DB 액세스와 모의 데이터 반환)를 변경할 수 있습니다. 약간 까다 롭지 만 충분히 고급 프로젝트에는 가치가 있습니다.


-1

IMHO 계약으로 디자인을 사용하면 매우 간단합니다. getter가 무엇을 제공해야하는지 결정하고 그에 따라 코드를 작성하십시오 (어딘가에 포함되거나 위임 될 수있는 간단한 코드 또는 복잡한 논리).


+1 : 동의합니다! 객체가 일부 데이터를 보유하려는 경우 게터는 객체의 현재 내용 만 반환해야합니다. 이 경우 데이터를로드하는 것은 다른 개체의 책임입니다. 계약서에 객체가 데이터베이스 레코드의 프록시라고 표시되면 getter는 즉시 데이터를 가져와야합니다. 데이터가로드되었지만 최신 상태가 아닌 경우 훨씬 더 복잡해질 수 있습니다. 데이터베이스의 변경 사항을 개체에 알려야합니까? 나는이 질문에 대한 독특한 대답이 없다고 생각합니다.
Giorgio
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.