임시 변수 대 라인 길이 요구 사항


10

Martin Fowler 's Refactoring을 읽고 있습니다. 일반적으로 우수하지만 Fowler의 권장 사항 중 하나가 약간의 문제를 일으키는 것으로 보입니다.

Fowler는 임시 변수를 쿼리 대신 다음과 같이 바꾸는 것이 좋습니다.

double getPrice() {
    final int basePrice = _quantity * _itemPrice;
    final double discountFactor;
    if (basePrice > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice * discountFactor;
}

당신은 도우미 메소드로 빠져 나옵니다.

double basePrice() {
    return _quantity * _itemPrice;
}

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice() * discountFactor;
}

일반적으로 임시 변수를 사용하는 한 가지 이유는 줄이 너무 길 때를 제외하고 동의합니다. 예를 들면 다음과 같습니다.

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

인라인을 시도하면 줄이 80자를 초과합니다.

대안으로 나는 코드 체인으로 끝납니다.

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

이 둘을 조정하기위한 전략은 무엇입니까?


10
80 문자는 내 모니터의 1의 약 1/3입니다. 80 개의 문자 줄을 고수하는 것이 여전히 가치가 있다고 확신하십니까?
jk.


당신 $host$uri예제는 일종의 고안입니다. 호스트가 설정이나 다른 입력에서 읽히지 않는 한, 랩이나 가장자리에서 벗어나더라도 같은 줄에있는 것이 좋습니다.
이즈 카타

5
그렇게 독단적 일 필요는 없습니다. 이 책은 언제 어디서나 적용해야하는 규칙이 아니라 도움이 될 때 사용할 수있는 기술 목록입니다. 요점은 코드를 유지 관리하기 쉽고 읽기 쉽게 만드는 것입니다. 리 팩터가이 작업을 수행하지 않으면 사용하지 않습니다.
Sean McSomething

80 자 제한이 약간 과도하다고 생각하지만 비슷한 제한 (100?)이 합리적입니다. 예를 들어, 나는 세로 방향 모니터에서 프로그래밍하는 것을 좋아하기 때문에 여분의 긴 줄이 성 가실 수 있습니다 (적어도 공통적이라면).
Thomas Eding

답변:


16

방법
1. 줄 길이 제한이 있으므로 더 많은 코드를 보고 이해할 수 있습니다. 그들은 여전히 ​​유효합니다.
2. 맹목적인 협약에 대한 판단을 강조한다 .
3. 성능을 최적화하지 않는 한 임시 변수를 피하십시오 .
4. 여러 줄 문장에서 정렬을 위해 들여 쓰기를 사용하지 마십시오.
5. 아이디어 경계를 따라 긴 문장을 여러 줄로 나눕니다 .

// prefer this
var distance = Math.Sqrt(
    Math.Pow(point2.GetX() - point1.GetX(), 2) + // x's
    Math.Pow(point2.GetY() - point1.GetY(), 2)   // y's
);

// over this
var distance = Math.Sqrt(Math.Pow(point2.GetX() -
    point1.GetX(), 2) + Math.Pow(point2.GetY() -
    point1.GetY(), 2)); // not even sure if I typed that correctly.

추론
임시 변수에 대한 (디버깅) 문제의 주요 원인은 변경 가능한 경향이 있다는 것입니다. 즉, 코드를 작성할 때 하나의 값이라고 가정하지만 함수가 복잡하면 다른 코드 조각이 상태를 반쯤 변경합니다. (또는 변수의 상태는 동일하지만 쿼리 결과는 변경된 대화).

성능을 최적화하지 않는 한 쿼리를 계속 사용하십시오 . 이렇게하면 해당 값을 계산하는 데 사용한 모든 논리가 한 곳에 유지됩니다.

당신이 제공 한 예제들 (자바와 ... PHP?)은 여러 줄로 된 문장을 허용합니다. 줄이 길어지면 끊어집니다. jquery 소스 는 이것을 극단으로 가져옵니다. (첫 번째 문장은 69 행으로 나옵니다!) 필자가 동의하지는 않지만 임시 변수를 사용하는 것보다 코드를 읽을 수있게하는 다른 방법이 있습니다.

몇 가지 예
1. 파이썬 용 PEP 8 스타일 가이드 (가장 예쁜 예는 아님)
2. 배 스타일 가이드의 Paul M Jones (로드 인수 중간)
3. Oracle 줄 길이 + 줄 바꿈 규칙 (80 자 유지를위한 유용한 전략)
4. MDN Java 실습 (컨벤션에 대한 프로그래머 판단 강화)


1
문제의 다른 부분은 임시 변수가 종종 그 값보다 오래 지속된다는 것입니다. 작은 스코프 블록에는 문제가 아니라 큰 블록에는 문제가 있습니다. 예, 큰 문제입니다.
로스 패터슨

8
임시 수정이 걱정되는 경우 const를 사용하십시오.
Thomas Eding

3

임시 변수 대신 도우미 메서드를 사용하는 가장 좋은 주장은 인간의 가독성이라고 생각합니다. 인간으로서 일시적인 varialbe보다 도우미-방법 체인을 읽는 데 더 많은 어려움이 있다면, 그것들을 추출해야 할 이유가 없습니다.

(내가 틀렸다면 나를 정정하십시오)


3

80 문자 지침을 엄격히 준수해야하거나 지역 임시 변수를 추출해야한다고 생각하지 않습니다. 그러나 같은 아이디어를 더 잘 표현할 수 있도록 긴 줄과 현지 온도를 조사해야합니다. 기본적으로, 그들은 주어진 기능이나 라인이 너무 복잡하다는 것을 나타내며 우리는 그것을 분해해야합니다. 그러나 작업을 나쁜 방식으로 분할하면 상황이 더 복잡해지기 때문에 조심해야합니다. 그래서 나는 일을 재개 가능하고 간단한 구성 요소로 나눌 것입니다.

게시 한 예를 살펴 보겠습니다.

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

모든 twilio API 호출은 "https://api.twilio.com/2010-04-1/"으로 시작하므로 재사용 할 수있는 기능이 매우 분명합니다.

$uri = twilioURL("Accounts/$accountSid/Usage/Records/AllTime")

실제로 URL을 생성하는 유일한 이유는 요청을하는 것이므로 다음과 같이합니다.

$response = TwilioApi::makeRequest("Accounts/$accountSid/Usage/Records/AllTime")

실제로 많은 URL이 실제로 "Accounts / $ accountSid"로 시작하므로 다음과 같이 추출 할 것입니다.

$response = TwilioApi::makeAccountRequest($accountSid, "Usage/Records/AllTime")

그리고 twilio api를 계정 번호를 보유한 객체로 만들면 다음과 같이 할 수 있습니다.

$response = $twilio->makeAccountRequest("Usage/Records/AllTime")

$ twilio 객체를 사용하면 단위 테스트를보다 쉽게 ​​수행 할 수 있다는 이점이 있습니다. 객체에 실제로 twilio를 호출하지 않는 다른 $ twilio 객체를 줄 수 있습니다. twilio는 더 빠르며 twilio에 이상한 일을하지 않습니다.

다른 하나를 보자

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

다음 중 하나에 대해 생각합니다.

$params = MustacheOptions::buildFromParams($bagcheck->getParams());

또는

$params = MustacheOptions::build($bagcheck->getFlatParams());

또는

$params = MustacheOptions::build(flatParams($backCheck));

어느 것이 더 재사용 가능한 숙어인지에 따라.


1

사실, 나는 일반적인 경우에 저명한 파울러 씨와 동의하지 않습니다.

이전에 인라인 된 코드에서 메소드를 추출 할 경우의 이점은 코드 재사용입니다. 메소드의 코드는 이제 초기 사용에서 분리되어 이제 코드를 복사하여 붙여 넣지 않고 다른 위치에서 사용할 수 있습니다 (복사 된 코드의 일반 논리를 변경해야하는 경우 여러 위치에서 변경해야 함) .

그러나 동일하고 종종 더 큰 개념적 가치는 "가치 재사용"입니다. Fowler는 추출 된 메소드를 호출하여 임시 변수를 "쿼리"로 바꿉니다. 더 효율적인 것은 무엇입니까? 특정 값이 필요할 때마다 데이터베이스에 여러 번 쿼리하거나 한 번 쿼리하고 결과를 저장하는 경우 (값이 변경되지 않을 정도로 정적 인 것으로 가정)?

귀하의 예에서 비교적 사소한 계산을 넘어서는 거의 모든 계산의 경우, 대부분의 언어에서 계산을 계속하는 것보다 하나의 계산 결과를 저장하는 것이 저렴합니다. 따라서 주문형을 다시 계산하기위한 일반적인 권장 사항은 불분명합니다. 더 많은 개발자 시간과 더 많은 CPU 시간이 소요되며 사소한 메모리를 절약 할 수 있습니다. 대부분의 최신 시스템에서는이 세 가지 중에서 가장 저렴한 리소스입니다.

이제 다른 코드와 함께 도우미 메서드를 "게으른"것으로 만들 수 있습니다. 처음 실행될 때 변수를 초기화합니다. 모든 추가 호출은 메소드가 명시 적으로 재 계산하도록 지시 될 때까지 해당 변수를 리턴합니다. 이것은 메소드의 매개 변수이거나이 메소드의 계산이 의존하는 값을 변경하는 다른 코드로 설정된 플래그 일 수 있습니다.

double? _basePrice; //not sure if Java has C#'s "nullable" concept
double basePrice(bool forceCalc)
{
   if(forceCalc || !_basePrice.HasValue)
      return _basePrice = _quantity * _itemPrice;
   return _basePrice.Value;
}

자,이 사소한 계산에는 저장보다 훨씬 많은 작업이 수행되므로 일반적으로 temp 변수를 사용하는 것이 좋습니다. 그러나 더 복잡한 계산을 위해서는 일반적으로 여러 번 실행을 피하고 코드의 여러 위치에서 필요로하는 것이 가장 좋습니다.


1

도우미 메서드는 가능하지만 데이터의 일관성을 유지하고 변수 범위를 불필요하게 늘리는 데주의해야합니다.

예를 들어, 자신의 예는 다음과 같습니다.

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;      <--- first call
    else discountFactor = 0.98;
    return basePrice() * discountFactor;                <--- second call
}

분명 모두 _quantity_itemPrice글로벌 변수는 (또는 적어도 클래스 레벨에서) 따라서 그들의 수정 외부 일 할 수있는 가능성이있다getPrice()

따라서 첫 번째 호출 basePrice()이 두 번째 호출과 다른 값을 반환 할 가능성이 있습니다 !

따라서 도우미 함수가 복잡한 수학을 분리하는 데 유용 할 수 있다고 제안하지만 지역 변수를 대체 할 때는 조심해야합니다.


당신은 또한 reductio ad absurdum 을 피해야합니다 -계산을 방법 discountFactor으로 가져야합니까? 따라서 귀하의 예는 다음과 같습니다.

double getPrice()
{
    final double basePrice      = calculateBasePrice();
    final double discountFactor = calculateDiscount( basePrice );

    return basePrice * discountFactor;
}

특정 수준 이상으로 분할하면 실제로 코드를 읽을 수 없게됩니다.


코드를 읽기 쉽게하기 위해 +1 오버 파티셔닝은 소스 코드가 해결하려는 비즈니스 문제를 숨길 수 있습니다. getPrice ()에 쿠폰이 적용되는 특별한 경우가있을 수 있지만, 함수 호출 체인에 숨겨져 있으면 비즈니스 규칙도 숨겨집니다.
Reactgular

0

명명 된 매개 변수 (ObjectiveC, Python, Ruby 등)가있는 언어로 작업하는 경우 임시 변수가 덜 유용합니다.

그러나 basePrice 예제에서 쿼리를 실행하는 데 시간이 걸리고 나중에 사용할 수 있도록 임시 변수에 결과를 저장하려고 할 수 있습니다.

당신처럼, 명확성과 선 길이 고려를 위해 임시 변수를 사용합니다.

또한 프로그래머가 PHP에서 다음을 수행하는 것을 보았습니다. 디버깅에는 흥미롭고 훌륭하지만 조금 이상합니다.

$rs = DB::query( $query = "SELECT * FROM table" );
if (DEBUG) echo $query;
// do something with $rs

0

이 권장 사항의 근거는 응용 프로그램의 다른 곳에서 동일한 사전 계산을 사용할 수 있어야한다는 것입니다. 리팩토링 패턴 카탈로그에서 Temp를 Query바꾸기를 참조 하십시오 .

그런 다음 새로운 방법을 다른 방법으로 사용할 수 있습니다

    double basePrice = _quantity * _itemPrice;
    if (basePrice > 1000)
        return basePrice * 0.95;
    else
        return basePrice * 0.98;

           http://i.stack.imgur.com/mKbQM.gif

    if (basePrice() > 1000)
        return basePrice() * 0.95;
    else
        return basePrice() * 0.98;
...
double basePrice() {
    return _quantity * _itemPrice;
}

따라서 호스트 및 URI 예제에서 동일한 URI 또는 ​​호스트 정의를 재사용하려는 경우에만이 권장 사항을 적용합니다.

이 경우 네임 스페이스로 인해 전역 uri () 또는 host () 메서드를 정의하지 않지만 twilio_host () 또는 archive_request_uri ()와 같은 자세한 정보가있는 이름을 정의합니다.

그런 다음 줄 길이 문제의 경우 몇 가지 옵션이 있습니다.

  • 과 같은 지역 변수를 만듭니다 uri = archive_request_uri().

이론적 근거 : 현재 방법에서 URI를 언급 한 것이 되길 원한다. URI 정의는 여전히 인수 분해됩니다.

  • 다음과 같은 로컬 메소드를 정의하십시오. uri() { return archive_request_uri() }

Fowler의 권장 사항을 자주 사용하는 경우 uri () 메서드가 동일한 패턴임을 알게됩니다.

언어 선택으로 인해 'self.'를 사용하여 로컬 메소드에 액세스 해야하는 경우 표현력을 높이기 위해 첫 번째 솔루션을 권장합니다 (Python에서는 현재 메소드 내에서 uri 함수를 정의합니다).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.