명확한 의미를 가진 두 가지 방법을 사용하거나 이중 사용 방법을 하나만 사용하는 것이 더 낫습니까?


30

인터페이스를 단순화하려면 getBalance()메소드 가없는 것이 더 낫 습니까? 전달 0받는 것은 charge(float c);같은 결과를 줄 것이다 :

public class Client {
    private float bal;
    float getBalance() { return bal; }
    float charge(float c) {
        bal -= c;
        return bal;
    }
}

어쩌면에 메모를 할 수 javadoc있습니까? 아니면 균형을 얻는 방법을 알아 내기 위해 클래스 사용자에게 맡기십시오.


26
나는 이것이 예제, 예시 코드이며 화폐 값을 저장하기 위해 부동 소수점 수학을 사용하지 않는다고 가정합니다. 그렇지 않으면 모든 설교를해야합니다. :-)
corsiKa

1
@corsiKa nah. 그것은 단지 예일뿐입니다. 그러나 네, 실제 클래스에서 돈을 표현하기 위해 float를 사용했을 것입니다 ... 부동 소수점 숫자의 위험에 대해 상기시켜 주셔서 감사합니다.
david

10
일부 언어는 뮤 테이터와 접근자를 구별하여 효과를 높입니다. 상수로만 액세스 할 수있는 인스턴스의 균형을 얻을 수 없다는 것은 짜증나게 될 것입니다!
JDługosz

답변:


90

인터페이스의 복잡성은 인터페이스의 요소 수 (이 경우 방법)에 의해 측정된다고 제안하는 것 같습니다. 많은 사람들은이 charge방법을 사용하여 Client추가 의 균형을 반환하는 데 사용될 수 있다는 점을 기억해야한다고 덧붙여 getBalance메소드 의 추가 요소를 갖는 것보다 훨씬 더 복잡합니다 . 인터페이스의 요소 수가 더 많더라도 모호성을 남기지 않는 지점에서 일을보다 명확하게하는 것이 훨씬 간단합니다.

또한 전화 는 분당 WTF (크린 코드에서 아래) 로 알려진 최소 놀랍게도charge(0)원칙을 위반하여 팀의 새 구성원 (또는 코드에서 약간 벗어난 후 현재 구성원)이 다음과 같이 할 때까지 어렵게합니다. 그들은 통화가 실제로 잔액을 얻는 데 사용된다는 것을 이해합니다. 다른 독자들이 어떤 반응을 보일지 생각해보십시오.

여기에 이미지 설명을 입력하십시오

또한이 charge메소드 의 서명은 객체가 새로운 값을 반환하면서 상태를 변경하게하기 때문에 단 하나의 작업명령-쿼리 분리 지침에 위배 됩니다 .

대체로이 경우 가장 간단한 인터페이스는 다음과 같습니다.

public class Client {
  private float bal;
  float getBalance() { return bal; }
  void charge(float c) { bal -= c; }
}

25
명령 쿼리 분리에 대한 요점은 IMO입니다. 이 프로젝트의 새로운 개발자라고 상상해보십시오. 코드를 살펴보면 getBalance()어떤 값 을 반환하고 아무것도 수정하지 않는 것이 분명합니다 . 반면에 charge(0)아마도 무언가를 수정하는 것처럼 보입니다 ... 아마도 PropertyChanged 이벤트를 보냅니 까? 이전 값 또는 새 값은 무엇을 반환합니까? 이제 문서에서 찾아보고 더 강력한 방법으로 피할 수 있었던 두뇌를 낭비해야합니다.
마법사 Xy

19
또한 사용 charge(0)이 더 효과가하는 일이 있기 때문에 균형을 얻을 수있는 방법으로 지금 이 구현 세부 사항을 사용자에게 weds 없다 청구 횟수 를 추적하는 것이 관련이 될 미래의 요구 사항을 쉽게 상상할 수 있습니다 . 당신은 인터페이스 요소를 제공해야 의미 고객이해야 할 일, 단순히 사람 할 일이 고객이해야 할 일을.
Ben

12
CQS 훈계가 반드시 적절한 것은 아닙니다. A에 대한 charge()이 방법은 시스템의 동시 원자 인 경우에있어서, 새로운 또는 오래된 값을 반환하는 것이 적절할 수있다.
Dietrich Epp

3
CQRS에 대한 두 가지 인용문이 매우 설득력있는 것으로 나타났습니다. 예를 들어, 나는 일반적으로 Meyer의 아이디어를 좋아하지만 함수의 리턴 유형을 살펴보고 그것이 무언가를 돌연변이시키는 지 여부는 임의적이고 견고하지 않습니다. 많은 동시 또는 불변 데이터 구조에는 돌연변이 + 리턴이 필요합니다. CQRS를 위반하지 않고 어떻게 문자열에 추가 하시겠습니까? 더 나은 인용이 있습니까?
user949300

6
명령 쿼리 분리를 따르는 코드는 거의 없습니다. 그것은 대중적인 객체 지향 언어에서 관용적이지 않으며 내가 사용한 모든 언어의 내장 API에 의해 위반됩니다. 이를 "모범 사례"로 묘사하는 것은 기괴한 것 같습니다. 실제로이 패턴을 따르는 하나의 소프트웨어 프로젝트의 이름을 지정할 수 있습니까?
Mark Amery

28

IMO, 교체 getBalance()charge(0)응용 프로그램을 통해 것은 단순화하지 않습니다. 예, 줄이 적지 만 charge()메소드 의 의미를 모호하게하여 사용자 또는 다른 사람 이이 코드를 다시 방문해야 할 때 두통을 일으킬 수 있습니다.

동일한 결과를 제공 할 수 있지만 계정 잔액을 얻는 것은 0의 청구액과 같지 않으므로 문제를 분리하는 것이 가장 좋습니다. 예를 들어, charge()계정 트랜잭션이있을 때마다 로그 를 수정하도록 수정 해야한다면 문제가 생겼으며 기능을 분리해야합니다.


13

코드는 자체 문서화되어야합니다. 내가 전화를 할 때 charge(x), 나는 기대 x충전 할 수 있습니다. 균형에 관한 정보는 부차적입니다. 또한 charge()전화를 걸 때 어떻게 구현 되는지 알 수 없으며 내일 어떻게 구현되는지 알 수 없습니다. 예를 들어, 다음에 대한이 잠재적 인 업데이트를 고려하십시오 charge().

float charge(float c) {
    lockDownUserAccountUntilChargeClears();
    bal -= c;
    Unlock();
    return bal;
}

charge()균형을 잡기 위해 갑자기 사용 하는 것은 그리 좋아 보이지 않습니다.


예! charge훨씬 더 무거운 좋은 점 .
중복 제거기

2

charge(0);잔액을 얻기 위해 사용 하는 것은 나쁜 생각입니다. 언젠가 누군가가 함수의 다른 사용을 실현하지 않고 부과되는 요금을 기록하기 위해 코드를 추가 한 다음 누군가가 잔액을 얻을 때마다 요금으로 기록됩니다. (이와 관련하여 다음과 같은 조건문과 같은 방법이 있습니다.

if (c > 0) {
    // make and log charge
}
return bal;

그러나 이들은 프로그래머가 구현을 알고 있다는 사실에 의존합니다. 필요하지 않다면 즉시 수행하지 않을 것입니다.

한마디로 : 사용자 나 프로그래머의 후계자에게 charge(0);균형을 잡는 올바른 방법 이라는 것을 깨닫지 말아야합니다. 가능한.


0

답변이 많지만 이에 대한 또 다른 이유 charge(0)는 간단한 오타로 charge(9)인해 고객이 잔액을 얻으려고 할 때마다 고객의 잔액이 줄어들 기 때문입니다. 적절한 단위 테스트를 수행하면 해당 위험을 완화 할 수 있지만 모든 호출에 부지런히 실패하면 charge이러한 사고가 발생할 수 있습니다.


-2

나는 특별한 경우 언급하고 싶은 , 적은 더 다목적을 가지고 이해를 방법 : 인 다형성, 많은 많은있을 경우 구현 이의 인터페이스 ; 특히 이러한 구현이 개별적으로 개발 된 코드로 동기화되어 업데이트 될 수없는 경우 (인터페이스는 라이브러리에 의해 정의 됨).

이 경우 각 구현을 작성하는 작업을 단순화하는 것이 구현의 명확성보다 훨씬 더 가치가 있습니다. 전자는 계약 위반 버그 (두 방법이 서로 일치하지 않음)를 피하는 반면, 후자는 가독성을 떨어 뜨리기 때문입니다. 로 정의 getBalance된 도우미 함수 또는 수퍼 클래스 메소드에 의해 복구됩니다 charge.

(이것은 디자인 패턴입니다. 구현 자 친화적 인 최소한의 인터페이스로 복잡한 호출자 친화적 인 인터페이스를 정의합니다. Classic Mac OS에서는 그리기 작업을위한 최소한의 인터페이스를 "병목 현상"이라고했습니다. 그러나 이것은 대중적인 용어는 아닙니다.)

그렇지 않은 경우 (구현이 거의 없거나 정확히 하나만있는 경우) 명확성을 위해 메소드를 분리하고에 0이 아닌 청구와 관련된 동작을 간단하게 추가 할 수 있도록하는 charge()것이 좋습니다.


1
실제로, 완벽한 커버리지 테스트를보다 쉽게하기 위해 더 작은 인터페이스를 선택한 사례가있었습니다. 그러나 이것이 반드시 클래스 사용자에게 노출되는 것은 아닙니다. 예를 들어 insert , append , delete 는 모든 대체 경로를 잘 연구하고 테스트 한 일반 대체 의 간단한 래퍼 일 수 있습니다 .
JDługosz

그런 API를 보았습니다. Lua 5.1+ 할당자는이를 사용합니다. 하나의 기능을 제공하고, 전달하는 매개 변수에 따라 새 메모리를 할당할지, 메모리를 할당 해제하거나, 기존 메모리에서 메모리를 재할 당할지 여부를 결정합니다 . 그러한 기능을 작성하는 것은 고통 스럽 습니다. 나는 오히려 Lua가 두 가지 기능, 즉 할당과 할당 해제를 위해 두 가지 기능을 제공했을 것입니다.
Nicol Bolas 2012

@NicolBolas : 그리고 재 할당이 없습니까? 어쨌든 그 realloc시스템은 때때로 일부 시스템 에서와 거의 동일한 "이점"을 추가했습니다 .
중복 제거기

@ 중복 제거기 : 할당 대 재 할당은 ... OK입니다. 그것들을 같은 기능에 넣는 것은 좋지 않지만, 같은 기능을 할당 해제하는 것만 큼 나쁘지는 않습니다. 또한 쉽게 구별 할 수 있습니다.
Nicol Bolas

나는 (재) 할당과 할당 해제를 병합하는 것이 이러한 유형의 인터페이스에서 특히 나쁜 예라고 말합니다. (할당은 계약이 매우 다릅니다 . 유효한 포인터 가 아닙니다 .)
Kevin Reid
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.