인스턴스 변수보다 로컬 변수를 선호하는 이유는 무엇입니까?


109

내가 작업하고있는 코드베이스는 인스턴스 변수를 자주 사용하여 다양한 사소한 메소드간에 데이터를 공유합니다. 원래 개발자는 이것이 Bob / Robert Martin 삼촌 의 Clean Code book에 언급 된 모범 사례를 준수한다는 것을 강력하게 인정합니다 . "기능의 첫 번째 규칙은 작을 것입니다." 그리고 "함수에 대한 이상적인 인수의 수는 0입니다 (나일론). (...) 인수는 어렵습니다. 많은 개념적 힘이 필요합니다."

예를 들면 :

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  private byte[] encodedData;
  private EncryptionInfo encryptionInfo;
  private EncryptedObject payloadOfResponse;
  private URI destinationURI;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    getEncodedData(encryptedRequest);
    getEncryptionInfo();
    getDestinationURI();
    passRequestToServiceClient();

    return cryptoService.encryptResponse(payloadOfResponse);
  }

  private void getEncodedData(EncryptedRequest encryptedRequest) {
    encodedData = cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private void getEncryptionInfo() {
    encryptionInfo = cryptoService.getEncryptionInfoForDefaultClient();
  }

  private void getDestinationURI() {
    destinationURI = router.getDestination().getUri();
  }

  private void passRequestToServiceClient() {
    payloadOfResponse = serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }
}

지역 변수를 사용하여 다음과 같이 리팩터링합니다.

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    byte[] encodedData = cryptoService.decryptRequest(encryptedRequest, byte[].class);
    EncryptionInfo encryptionInfo = cryptoService.getEncryptionInfoForDefaultClient();
    URI destinationURI = router.getDestination().getUri();
    EncryptedObject payloadOfResponse = serviceClient.handle(destinationURI, encodedData,
      encryptionInfo);

    return cryptoService.encryptResponse(payloadOfResponse);
  }
}

이것은 더 짧고, 다양한 사소한 방법들 사이의 암시 적 데이터 결합을 제거하고 변수 범위를 필요한 최소로 제한합니다. 그러나 이러한 이점에도 불구하고 위에서 언급 한 Bob 아저씨의 관행에 위배되는 것처럼이 리팩토링이 필요하다는 것을 원래 개발자에게 설득 할 수없는 것 같습니다.

따라서 내 질문 : 인스턴스 변수보다 로컬 변수를 선호하는 객관적이고 과학적인 근거는 무엇입니까? 손가락을 대지 못하는 것 같습니다. 내 직감 은 숨겨진 커플 링이 나쁘고 좁은 범위가 넓은 것보다 낫다는 것을 알려줍니다. 그러나 이것을 뒷받침하는 과학은 무엇입니까?

반대로, 내가 간과 한이 리팩토링에 단점이 있습니까?


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
maple_shaft

답변:


170

인스턴스 변수보다 로컬 변수를 선호하는 목적, 과학적 근거는 무엇입니까?

범위는 이진 상태가 아니며 그라디언트입니다. 가장 큰 것에서 가장 작은 것까지 순위를 매길 수 있습니다.

Global > Class > Local (method) > Local (code block, e.g. if, for, ...)

편집 : 내가 "클래스 범위"라고 부르는 것은 "인스턴스 변수"의 의미입니다. 내 지식으로는 그것들은 동의어이지만 Java 개발자가 아닌 C # 개발자입니다. 간결성을 위해 정적은 문제의 주제가 아니기 때문에 모든 정적을 전역 범주로 묶었습니다.

범위가 작을수록 좋습니다. 이론적 근거는 변수가 가능한 가장 작은 범위 내에 있어야한다는 것 입니다. 이것에 많은 이점이 있습니다 :

  • 그것은 당신이 현재 클래스의 책임에 대해 생각하도록 강요하고 SRP를 고수하도록 도와줍니다.
  • 전역 명명 충돌을 피할 필요가 없습니다. 예를 들어, 둘 이상의 클래스에 Name속성 이있는 FooName경우 BarName,, ... 처럼 접두사를 붙일 필요가 없습니다 . 따라서 변수 이름을 최대한 깨끗하고 간결하게 유지하십시오.
  • 사용 가능한 변수 (예 : Intellisense)를 상황에 맞는 변수로 제한하여 코드를 정리합니다.
  • 그것은 어떤 형태의 액세스 제어를 가능하게하기 때문에 당신이 모르는 배우 (예를 들어 동료가 개발 한 다른 클래스)가 데이터를 조작 할 수 없습니다.
  • 이러한 변수의 선언이 가능한 한 이러한 변수의 실제 사용에 가깝게 유지되도록 코드를 더 읽기 쉽게 만듭니다.
  • 지나치게 넓은 범위에서 변수를 선언하려는 경우 OOP를 잘 이해하지 못하는 개발자 또는 구현 방법을 나타내는 경우가 많습니다. 지나치게 넓은 범위의 변수를 보는 것은 OOP 접근 방식 (일반적으로 개발자 또는 특정 코드베이스)에 문제가 있음을 나타내는 붉은 깃발 역할을합니다.
  • (케빈의 의견) 지역 주민을 사용하면 올바른 순서대로 일을해야합니다. 원래 (클래스 변수) 코드 passRequestToServiceClient()에서 메소드의 맨 위로 잘못 이동할 수 있으며 여전히 컴파일됩니다. 지역 변수를 사용하면 초기화되지 않은 변수를 전달한 경우에만 실수를 할 수 있습니다. 실제로 실제로 수행하지 않을 정도로 분명합니다.

그러나 이러한 이점에도 불구하고 위에서 언급 한 Bob 아저씨의 관행에 위배되는 것처럼이 리팩토링이 필요하다는 것을 원래 개발자에게 설득 할 수없는 것 같습니다.

반대로, 내가 간과 한이 리팩토링에 단점이 있습니까?

여기서 문제는 로컬 변수에 대한 인수가 유효하지만 올바르지 않은 추가 변경을 수행하여 제안 된 수정 프로그램이 냄새 테스트를 실패하게한다는 것입니다.

귀하의 "클래스 변수 없음"제안을 이해하고 그에 대한 장점이 있지만 실제로 메소드 자체도 제거했으며 완전히 다른 볼 게임입니다. 메소드는 그대로 있어야하며 대신 클래스 변수에 저장하지 않고 값을 리턴하도록 변경해야합니다.

private byte[] getEncodedData() {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
}

private EncryptionInfo getEncryptionInfo() {
    return cryptoService.getEncryptionInfoForDefaultClient();
}

// and so on...

나는 당신이 process방법 에서 한 일에 동의 하지만, 당신은 그들의 몸을 직접 실행하는 대신 개인 하위 메소드를 호출해야했습니다.

public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    byte[] encodedData = getEncodedData();
    EncryptionInfo encryptionInfo = getEncryptionInfo();

    //and so on...

    return cryptoService.encryptResponse(payloadOfResponse);
}

특히 여러 번 재사용해야하는 메소드를 실행할 때 추가 추상화 계층을 원할 것입니다. 현재 메소드를 재사용하지 않더라도 코드 가독성을 돕기 위해 해당되는 경우 하위 메소드를 이미 작성하는 것이 여전히 좋은 습관입니다.

로컬 변수 인수에 관계없이 제안 된 수정 프로그램이 원본보다 상당히 읽기 어렵다는 것을 즉시 알았습니다. 클래스 변수를 원하는대로 사용하면 코드 가독성이 떨어지지 만 모든 논리를 단일 (지금은 긴 바람) 방법으로 쌓아 놓은 것과 비교할 때 언뜻 보지 못합니다.


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
maple_shaft

79

원래 코드는 인수와 같은 멤버 변수를 사용하고 있습니다. 그가 인수의 수를 최소화한다고 말하면, 실제로 의미하는 것은 기능을 수행하기 위해 메소드가 필요로하는 데이터의 양을 최소화하는 것입니다. 해당 데이터를 멤버 변수에 추가해도 아무런 효과가 없습니다.


20
전적으로 동의합니다! 이러한 멤버 변수는 단순히 암시적인 함수 인수입니다. 실제로 변수 값과 함수 사용 (외부 POV에서) 사이에 명시적인 연결이 없기 때문에 상황이 더 나빠집니다.
Rémi

1
나는 이것이 책의 의미가 아니라고 말할 것입니다. 정확히 0의 입력 데이터가 필요한 기능은 몇 개입니까? 이 책이 잘못되었다고 생각하는 것 중 하나입니다.
Qwertie

1
@Qwertie 객체가 있으면 작동하는 데이터가 객체 안에 완전히 캡슐화 될 수 있습니다. 너무 이상하게 보이 process.Start();거나 myString.ToLowerCase()이상하게 보이지 않아야하는 함수 (실제로 이해하기 가장 쉬운 것)
R. Schmitz

5
둘 다 하나의 주장을 내포하고있다 this. 점 앞에이 주장이 명시 적으로 주어진다고 주장 할 수도있다.
BlackJack

47

다른 답변은 이미 지역 변수의 이점을 완벽하게 설명 했으므로 남아있는 것은 모두 귀하의 질문의 일부입니다.

그러나 이러한 이점에도 불구하고 위에서 언급 한 Bob 아저씨의 관행에 위배되는 것처럼이 리팩토링이 필요하다는 것을 원래 개발자에게 설득 할 수없는 것 같습니다.

쉬워야합니다. 밥 삼촌의 클린 코드에서 다음 인용문을 가리 키기 만하면됩니다.

부작용이 없다

부작용은 거짓말입니다. 함수는 한 가지 일을 약속하지만 다른 숨겨진 일도합니다. 때로는 자체 클래스의 변수를 예기치 않게 변경합니다. 때로는 함수 또는 시스템 전역으로 전달되는 매개 변수로 만들 수도 있습니다. 두 경우 모두 악의적이고 악의적 인 악의로 인해 종종 이상한 시간적 결합과 순서 의존성을 초래합니다.

(예 생략)

이 부작용은 일시적인 결합을 만듭니다. 즉, checkPassword는 특정 시간 (즉, 세션을 초기화해도 안전한 경우)에만 호출 할 수 있습니다. 순서가 맞지 않으면 세션 데이터가 실수로 손실 될 수 있습니다. 일시적인 커플 링은 특히 부작용으로 숨겨져있을 때 혼동됩니다. 시간 결합이 있어야하는 경우 함수 이름을 명확히해야합니다. 이 경우 checkPasswordAndInitializeSession 함수의 이름을 바꿀 수 있지만“한 가지 일”을 위반하는 것은 확실합니다.

즉, Uncle Bob은 함수가 인수를 거의 취하지 않아야한다고 말하지 않으며, 가능할 때마다 함수가 로컬이 아닌 상태와의 상호 작용을 피해야한다고 말합니다.


3
"현지 세계"에서는 이것이 두 번째로 제시된 답변입니다. 동료가 이성을 듣는 이상적인 상황에 대한 첫 번째 대답입니다. 그러나 동료가 열성적인 사람이라면이 대답은 너무 많은 퍼지없이 상황을 처리합니다.
R. Schmitz

2
이 아이디어에 대한 실제적인 변형을 위해 인스턴스 또는 글로벌 상태보다 로컬 상태에 대해 추론하기가 훨씬 쉽습니다. 잘 정의되고 엄격하게 포함 된 변경 가능성과 부작용은 거의 문제를 일으키지 않습니다. 예를 들어, 많은 정렬 기능이 부작용을 통해 제자리에서 작동하지만 로컬 범위에서 쉽게 추론 할 수 있습니다.
Beefster

1
아, 좋은 노인은 "모순 된 공리주의에서, 무엇이든 증명할 수 있습니다". 어려운 진실이없고 IRL이 있기 때문에 모든 교리에는 반대되는 것, 즉 모순되는 진술이 포함되어야합니다.
ivan_pozdeev

26

"그것은 누군가의 삼촌이 생각하는 것과 모순된다"는 결코 좋은 주장이 아니다. 못. 삼촌에게서 지혜를 얻지 말고 스스로 생각하십시오.

즉, 인스턴스 변수는 실제로 영구적으로 또는 반영구적으로 저장해야하는 정보를 저장하는 데 사용해야합니다. 여기에 정보가 없습니다. 인스턴스 변수없이 사는 것은 매우 간단하므로 갈 수 있습니다.

테스트 : 각 인스턴스 변수에 대한 문서 주석을 작성하십시오. 완전히 무의미하지 않은 것을 쓸 수 있습니까? 그리고 네 명의 접근 자에게 문서 주석을 작성하십시오. 그들은 똑같이 무의미합니다.

최악의 경우, 다른 cryptoService를 사용하기 때문에 변경 사항을 해독하는 방법을 가정하십시오. 4 줄의 코드를 변경하는 대신 4 개의 인스턴스 변수를 다른 것으로 바꾸고 4 개의 게터를 다른 것으로 바꾸고 4 줄의 코드를 바꿔야합니다.

물론 코드 라인으로 지불하는 경우 첫 번째 버전이 바람직합니다. 11 줄 대신 31 줄. 무언가를 디버깅 할 때 읽고, 변경이 필요할 때 적응하고, 두 번째 cryptoService를 지원하는 경우 복제하기 위해 작성하고 영원히 유지하기 위해 3 배 더 많은 행이 있습니다.

(로컬 변수를 사용하면 올바른 순서로 호출해야한다는 중요한 점이 없습니다.)


16
자신을 생각하는 것은 물론 좋습니다. 그러나 첫 오프닝 단락에는 교사 또는 노인의 의견을 기각하는 학습자 또는 후배가 효과적으로 포함됩니다. 너무 멀어요
7

9
@Flater 교사 나 노인의 의견에 대해 생각하고 그들이 틀린 것을 본 후에 그들의 의견을 무시하는 것이 유일한 일입니다. 결국, 그것은 기각에 관한 것이 아니라 그것을 심문하고 확실하게 잘못 된 경우에만 기각하는 것에 관한 것입니다.
glglgl

10
@ChristianHackl : 나는 전통주의를 맹목적으로 따르지 않고 완전히 탑승하고 있지만, 맹목적으로 그것을 무시하지는 않을 것입니다. 그 대답은 자신의 견해에 찬성하여 습득 한 지식을 피할 수있는 것처럼 보이며, 건전한 접근 방식은 아닙니다. 맹목적으로 다른 사람들의 의견을 따르십시오. 당신이 동의하지 않을 때 무언가에 의문을 제기하십시오. 당신이 동의하지 않기 때문에 완전히 무시합니다. 내가 읽을 때 적어도 대답의 첫 번째 단락은 후자를 제안하는 것으로 보입니다 . 정교함이 필요한 "자신을 생각하십시오"라는 말이 무엇인지에 달려 있습니다.
10

9
공유 지혜 플랫폼에서, 이것은 조금 벗어난 것 같습니다 ...
drjpizzle

4
결코 좋은 주장이 아닙니다. 나는 규칙이 규정하지 말고 지시하기 위해 존재한다는 것에 전적으로 동의합니다. 그러나 규칙에 대한 규칙은 규칙의 하위 클래스에
지나지 않으므로

14

인스턴스 변수보다 로컬 변수를 선호하는 목적, 과학적 근거는 무엇입니까? 손가락을 대지 못하는 것 같습니다. 내 직감은 숨겨진 커플 링이 나쁘고 좁은 범위가 넓은 것보다 낫다는 것을 알려줍니다. 그러나 이것을 뒷받침하는 과학은 무엇입니까?

인스턴스 변수는 호스트 객체의 속성 을 나타 내기위한 것이며 객체 자체보다 범위가 더 좁은 계산 스레드와 관련된 속성을 나타내는 것은 아닙니다 . 아직 다루지 않은 것으로 보이는 구별을 이끌어내는 이유 중 일부는 동시성과 재진입에 관한 것입니다. 메소드가 인스턴스 변수의 값을 설정하여 데이터를 교환하면 두 개의 동시 스레드가 해당 인스턴스 변수에 대한 서로의 값을 쉽게 복제하여 간헐적이고 찾기 어려운 버그를 생성 할 수 있습니다.

인스턴스 변수에 의존하는 데이터 교환 패턴이 메소드를 재진입 할 ​​수 없게 할 위험이 높기 때문에 하나의 스레드만으로도 이러한 행을 따라 문제점이 발생할 수 있습니다. 마찬가지로, 동일한 변수를 사용하여 서로 다른 메소드 쌍간에 데이터를 전달하는 경우 비재 귀적 메소드 호출 체인을 실행하는 단일 스레드가 포함 된 인스턴스 변수의 예기치 않은 수정을 중심으로 버그가 발생할 위험이 있습니다.

이러한 시나리오에서 안정적으로 정확한 결과를 얻으려면 별도의 변수를 사용하여 하나의 메소드를 호출하는 각 메소드 쌍간에 통신하거나 모든 메소드 구현에서 다른 모든 메소드의 모든 구현 세부 사항을 고려해야합니다. 직접 또는 간접적으로 호출하는 메소드. 부서지기 쉬우 며 확장 성이 떨어집니다.


7
지금까지 이것은 스레드 안전성과 동시성을 언급하는 유일한 대답입니다. 문제의 특정 코드 예제를 감안할 때 놀랍습니다. SomeBusinessProcess 인스턴스는 여러 암호화 된 요청을 한 번에 안전하게 처리 할 수 ​​없습니다. 이 메소드 public EncryptedResponse process(EncryptedRequest encryptedRequest)는 동기화되지 않았으며 동시 호출은 인스턴스 변수의 값을 클로버 할 수 있습니다. 이것은 좋은 지적입니다.
Joshua Taylor

9

단지 토론하면서 process(...), 동료의 예는 비즈니스 로직의 의미에서 훨씬 더 읽기 쉽습니다. 반대로 카운터 예제는 의미를 추출하기 위해 단순한 시각 이상을 필요로합니다.

즉, 깨끗한 코드는 읽기 쉽고 좋은 품질입니다. 로컬 상태를보다 글로벌 한 공간으로 밀어내는 것은 고급 어셈블리이므로 품질에는 제로입니다.

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest request) {
    checkNotNull(encryptedRequest);

    return encryptResponse
      (routeTo
         ( destination()
         , requestData(request)
         , destinationEncryption()
         )
      );
  }

  private byte[] requestData(EncryptedRequest encryptedRequest) {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private EncryptionInfo destinationEncryption() {
    return cryptoService.getEncryptionInfoForDefaultClient();
  }

  private URI destination() {
    return router.getDestination().getUri();
  }

  private EncryptedObject routeTo(URI destinationURI, byte[] encodedData, EncryptionInfo encryptionInfo) {
    return serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }

  private void encryptResponse(EncryptedObject payloadOfResponse) {
    return cryptoService.encryptResponse(payloadOfResponse);
  }
}

이것은 모든 범위에서 변수의 필요성을 제거하는 표현입니다. 그렇습니다. 컴파일러는 그것들을 생성하지만 중요한 부분은 코드가 효율적이되도록 제어한다는 것입니다. 비교적 읽기 쉬운 반면.

명명에 대한 요점. 의미가 있고 이미 사용 가능한 정보를 확장하는 가장 짧은 이름을 원합니다. 즉. destinationURI에서 'URI'는 이미 유형 서명으로 알려져 있습니다.


4
모든 변수를 제거한다고해서 코드를 쉽게 읽을 수있는 것은 아닙니다.
Pharap

포인트 프리 스타일 en.wikipedia.org/wiki/Tacit_programming
Marcin

@Pharap 사실, 변수가 부족해도 가독성이 보장되지 않습니다. 경우에 따라 디버깅이 더 어려워 질 수도 있습니다. 요점은 잘 선택된 이름, 명확한 표현의 사용법은 여전히 ​​효율적이지만 아이디어를 매우 명확하게 전달할 수 있다는 것입니다.
Kain0_0

7

이 변수와 개인 메소드를 모두 제거합니다. 내 리 팩터는 다음과 같습니다.

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    return cryptoService.encryptResponse(
        serviceClient.handle(router.getDestination().getUri(),
        cryptoService.decryptRequest(encryptedRequest, byte[].class),
        cryptoService.getEncryptionInfoForDefaultClient()));
  }
}

비공개 메소드의 경우, 예를 들어 router.getDestination().getUri()보다 명확하고 읽기 쉽습니다 getDestinationURI(). 같은 클래스에서 같은 줄을 두 번 사용하는 경우에도 반복합니다. 다른 방법으로 보면 a가 필요한 경우 getDestinationURI()클래스가 아닌 다른 클래스에 속할 수 SomeBusinessProcess있습니다.

변수와 속성의 경우 일반적으로 필요한 값은 나중에 사용하기 위해 값을 보유하는 것입니다. 클래스에 속성에 대한 공용 인터페이스가없는 경우 속성이 아니어야합니다. 클래스 속성의 최악의 종류는 아마도 부작용을 통해 개인 메소드간에 값을 전달하는 것입니다.

어쨌든 클래스는해야 할 필요 process()가 있으며 객체는 버려지고 메모리에 상태를 유지할 필요가 없습니다. 추가 리팩터링 가능성은 해당 클래스에서 CryptoService를 제거하는 것입니다.

의견을 바탕 으로이 답변을 실제 사례를 기반으로 추가하고 싶습니다. 실제로 코드 검토에서 가장 먼저 선택해야 할 것은 클래스를 리팩터링하고 암호화 / 복호화 작업을 옮기는 것입니다. 일단 완료되면 메소드와 변수가 필요한지, 이름이 올바르게 지정되는지 묻습니다. 마지막 코드는 아마도 이것에 더 가깝습니다.

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;

  public Response process(Request request) {
    return serviceClient.handle(router.getDestination().getUri());
  }
}

위의 코드를 사용하면 추가 리 팩터가 필요하지 않다고 생각합니다. 규칙과 마찬가지로 적용시기와시기를 아는 데는 경험이 필요하다고 생각합니다. 규칙은 모든 상황에서 작동하는 것으로 입증 된 이론이 아닙니다.

반면에 코드 검토는 코드 조각이 통과하기까지 걸리는 시간에 실제로 영향을 미칩니다. 내 트릭은 코드가 적고 이해하기 쉽게 만드는 것입니다. 리뷰어를 제거 할 수 있다면 변수 이름은 토론의 요점이 될 수 있습니다.


많은 사람들이 주저 할 것이지만 저의 공감대입니다. 물론 일부 추상화는 의미가 있습니다. (BTW "프로세스"라는 방법은 끔찍합니다.) 그러나 여기서 논리는 최소화됩니다. 그러나 OP의 질문은 전체 코드 스타일에 관한 것이며 경우가 더 복잡 할 수 있습니다.
Joop Eggen

1
모든 것을 하나의 메소드 호출로 연결하는 데있어 분명한 한 가지 문제는 가독성입니다. 주어진 객체에 대해 둘 이상의 작업이 필요한 경우에도 작동하지 않습니다. 또한 작업을 단계별로 수행하고 개체를 검사 할 수 없기 때문에 디버깅이 거의 불가능합니다. 이것은 기술 수준에서 작동하지만 소프트웨어 개발의 비 런타임 측면을 크게 무시하므로 이것을 옹호하지 않습니다.
10

@Flater 나는 당신의 의견에 동의합니다, 우리는 이것을 어디에나 적용하고 싶지 않습니다. 나는 실제 입장을 명확히하기 위해 대답을 편집했다. 내가 보여주고 싶은 것은 실제로 규칙이 적절할 때만 적용한다는 것입니다. 이 인스턴스에서 체인 메소드 호출은 정상이며 디버그해야 할 경우 체인 메소드에 대한 테스트를 호출합니다.
imel96

@JoopEggen 네, 추상화는 의미가 있습니다. 예를 들어, 개인 메소드는 어쨌든 추상화를 제공하지 않으며 클래스 사용자는 해당 클래스를 알지 못합니다.
imel96

1
@ imel96 ServiceClient와 CryptoService의 결합으로 인해 SBP 대신 SC에 CS를 주입하여 더 높은 아키텍처 수준에서 근본적인 문제를 해결하는 데 집중할 가치가 있음을 알게 된 소수의 사람들 중 한 명인 것은 아마도 재미 있습니다. 이것이 바로이 이야기의 핵심입니다. 세부 사항에 초점을 맞추면서 큰 그림을 추적하기가 너무 쉽습니다.
vaxquis

4

Flater의 답변 은 범위 지정 문제를 잘 다루지 만 여기에도 다른 문제가 있다고 생각합니다.

데이터처리 하는 함수와 단순히 데이터에 액세스 하는 함수 에는 차이가 있습니다 .

전자는 실제 비즈니스 로직을 실행하지만 후자는 타이핑을 줄이고 더 단순하고 재사용 가능한 인터페이스를 추가하여 안전성을 추가합니다.

이 경우 데이터 액세스 기능은 타이핑을 저장하지 않으며 다른 곳에서 재사용되지 않습니다 (또는 제거하는 데 다른 문제가 있음). 따라서 이러한 기능은 존재하지 않아야합니다.

비즈니스 로직 만 명명 된 함수로 유지함으로써 우리는 Flater 's answerimel96 ' s answer 사이의 두 세계를 최대한 활용할 수 있습니다 .

public EncryptedResponse process(EncryptedRequest encryptedRequest) {

    byte[] requestData = decryptRequest(encryptedRequest);
    EncryptedObject responseData = handleRequest(router.getDestination().getUri(), requestData, cryptoService.getEncryptionInfoForDefaultClient());
    EncryptedResponse response = encryptResponse(responseData);

    return response;
}

// define: decryptRequest(), handleRequest(), encryptResponse()

3

가장 중요하고 중요한 것 : 밥 삼촌은 때때로 설교자처럼 보이지만 그의 규칙에는 예외가 있다고 말합니다.

Clean Code의 전체 아이디어는 가독성을 높이고 오류를 피하는 것입니다. 서로를 위반하는 몇 가지 규칙이 있습니다.

함수에 대한 그의 주장은 닐라 딕 함수가 가장 좋지만 최대 3 개의 매개 변수를 사용할 수 있다는 것입니다. 나는 개인적으로 4도 괜찮다고 생각합니다.

인스턴스 변수를 사용할 때는 일관성있는 클래스를 만들어야합니다. 즉, 변수는 모든 비 정적 방법은 아니지만 많은 방법으로 사용해야합니다.

클래스의 많은 곳에서 사용되지 않는 변수는 이동해야합니다.

나는 원래 버전이나 리팩터링 된 버전을 최적으로 고려하지 않았으며 @Flater는 이미 반환 값으로 수행 할 수있는 것을 매우 잘 설명했습니다. 가독성을 향상시키고 리턴 값을 사용하기위한 오류를 줄입니다.


1

지역 변수는 범위를 줄여서 변수를 사용할 수있는 방법을 제한하므로 특정 클래스의 오류를 방지하고 가독성을 향상시킵니다.

인스턴스 변수는 함수 호출 방식을 줄여 특정 클래스의 오류를 줄이고 가독성을 향상시킵니다.

하나가 옳고 다른 하나가 잘못되었다고 말하는 것은 특정 경우에 유효한 결론 일 수 있지만 일반적인 조언으로는 ...

TL; DR : 열심이 너무 강한 이유는 열심이 너무 많다고 생각합니다.


0

get ...로 시작하는 메소드가 void를 리턴하지 않아야한다는 사실에도 불구하고 메소드 내 추상화 레벨의 분리는 첫 번째 솔루션에 제공됩니다. 두 번째 솔루션의 범위가 더 넓어 지더라도이 방법에서 어떤 일이 일어나고 있는지 추론하기는 여전히 어렵습니다. 여기서는 지역 변수를 할당 할 필요가 없습니다. 메소드 이름을 유지하고 코드를 다음과 같이 리팩터링합니다.

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    return getEncryptedResponse(
            passRequestToServiceClient(getDestinationURI(), getEncodedData(encryptedRequest) getEncryptionInfo())
        );
  }

  private EncryptedResponse getEncryptedResponse(EncryptedObject encryptedObject) {
    return cryptoService.encryptResponse(encryptedObject);
  }

  private byte[] getEncodedData(EncryptedRequest encryptedRequest) {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private EncryptionInfo getEncryptionInfo() {
    return cryptoService.getEncryptionInfoForDefaultClient();
  }

  private URI getDestinationURI() {
    return router.getDestination().getUri();
  }

  private EncryptedObject passRequestToServiceClient(URI destinationURI, byte[] encodedData, EncryptionInfo encryptionInfo) {
    return serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }
}

0

둘 다 똑같이하고 성능 차이는 눈에 띄지 않으므로 과학적 이라고 생각하지 않습니다. 주장 합니다. 그것은 주관적인 선호로 귀결됩니다.

그리고 나도 당신의 동료보다 당신의 길을 더 좋아하는 경향이 있습니다. 왜? 책 저자의 말에도 불구하고 읽고 이해하기가 더 쉽다고 생각하기 때문입니다.

두 방법 모두 같은 것을 달성하지만 그의 방법은 더 널리 퍼져 있습니다. 이 코드를 읽으려면 여러 함수와 멤버 변수 사이를왔다 갔다해야합니다. 한곳에서 모두 응축되지는 않습니다. 머리를 이해하기 위해서는 모든 것을 기억해야합니다. 훨씬 더 큰인지 부하입니다.

대조적으로, 당신의 접근 방식은 모든 것을 훨씬 더 조밀하게 포장하지만, 그것을 뚫을 수 없도록 만들지는 않습니다. 당신은 단지 그것을 한 줄씩 읽고, 그것을 이해하기 위해 그렇게 많이 암기 할 필요가 없습니다.

그러나 그가 그런 방식으로 코드를 작성 하는 데 익숙 하다면 , 그에게 다른 방법 일 수 있다고 상상할 수 있습니다.


그것은 흐름을 이해하는 데있어 가장 큰 장애물로 입증되었습니다. 이 예제는 매우 작지만 다른 예제는 중간 결과에 35 (!) 인스턴스 변수를 사용했지만 종속성을 포함하는 12 개의 인스턴스 변수는 계산하지 않았습니다. 이전에 이미 설정 한 내용을 추적해야하므로 따라 가기가 매우 어려웠습니다. 일부는 나중에 재사용되어 더 어려워졌습니다. 운 좋게도 여기에 제시된 주장은 마침내 동료가 리팩토링에 동의하도록 설득했습니다.
Alexander
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.