태그 보관물: refactoring

refactoring

인스턴스 변수보다 로컬 변수를 선호하는 이유는 무엇입니까? { checkNotNull(encryptedRequest);

내가 작업하고있는 코드베이스는 인스턴스 변수를 자주 사용하여 다양한 사소한 메소드간에 데이터를 공유합니다. 원래 개발자는 이것이 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 아저씨의 관행에 위배되는 것처럼이 리팩토링이 필요하다는 것을 원래 개발자에게 설득 할 수없는 것 같습니다.

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

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



답변

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

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

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

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

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


답변

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


답변

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

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

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

부작용이 없다

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

(예 생략)

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

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


답변

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

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

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

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

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

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


답변

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

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

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

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


답변

단지 토론하면서 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’는 이미 유형 서명으로 알려져 있습니다.


답변

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

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());
  }
}

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

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