Clean Code-리터럴 1을 상수로 변경해야합니까?


14

마법의 숫자를 피하기 위해 문자 그대로 의미있는 이름을 부여해야한다는 말을 자주 듣습니다. 같은 :

//THIS CODE COMES FROM THE CLEAN CODE BOOK
for (int j = 0; j < 34; j++) {
    s += (t[j] * 4) / 5;
}

-------------------- Change to --------------------

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j = 0; j < NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

다음과 같은 더미 방법이 있습니다.

설명한다 : 나는 사람들에게 봉사 할 명단을 가지고 있다고 가정하고 기본적으로 우리는 음식 만 구매하기 위해 5 달러를 소비하지만, 한 명 이상이있을 때 물과 음식을 사야한다면 더 많은 돈을 소비해야한다. 아마도 6 달러가 될 것이다. 코드를 변경 하고 리터럴 1에 집중 하십시오.

public int getMoneyByPersons(){
    if(persons.size() == 1){ 
        // TODO - return money for one person
    } else {
        // TODO - calculate and return money for people.
    }

}

친구에게 내 코드를 검토하도록 요청했을 때 하나는 값 1의 이름을 지정하면 더 깨끗한 코드가 생성된다고 말하고 다른 하나는 값 자체가 의미가 있기 때문에 상수 이름이 필요하지 않다고 말했습니다.

그래서 내 질문은 리터럴 값 1의 이름을 지정해야합니까?입니다. 값은 언제 마법의 숫자입니까? 최상의 솔루션을 선택하기 위해 컨텍스트를 어떻게 구별 할 수 있습니까?


어디 persons에서 왔으며 무엇을 설명합니까? 코드에는 주석이 없으므로 수행중인 작업을 추측하기가 어렵습니다.
휴 버트 Grzeskowiak

7
1보다 명확하지 않습니다. "size"를 "count"로 변경합니다. 그리고 moneyService는 어리석은 사람이므로 개인 수집에 따라 반환 할 금액을 결정할 수 있어야합니다. 그래서 사람을 getMoney에 전달하고 예외적 인 경우를 정리하도록하겠습니다.
Martin Maat

4
if 절에서 논리 메소드를 새로운 메소드로 추출 할 수도 있습니다. 아마도 IsSinglePerson ()이라고 부를 수 있습니다. 이렇게하면 하나의 변수보다 조금 더 추출 할 수 있지만 if 절을 좀 더 읽기 쉽게 만들 수 있습니다.
selmaohneh


2
아마도이 논리가 moneyService.getMoney ()에 더 적합할까요? 한 사람의 경우 getMoney에 전화해야 할 순간이 있습니까? 그러나 나는 1이라는 것이 분명하다는 일반적인 감정에 동의 할 것입니다. 매직 넘버는 프로그래머가 어떻게 그 숫자에 도달했는지를 묻는 머리를 긁어 야하는 숫자입니다. 즉if(getErrorCode().equals(4095)) ...
Neil

답변:


23

아니요.이 예에서 1은 완벽하게 의미가 있습니다.

그러나 persons.size ()가 0이면 어떻게됩니까? persons.getMoney()0과 2에서는 작동하지만 1 에서는 작동하지 않는 이상한 것처럼 보입니다 .


나는 1이 의미가 있다는 것에 동의합니다. 감사합니다. 그런데 질문을 업데이트했습니다. 내 아이디어를 얻기 위해 다시 볼 수 있습니다.
Jack

3
수년에 걸쳐 여러 번 들었던 문구는 매직 넘버가 0과 1 이외의 이름이없는 상수 라는 것입니다.이 숫자의 이름을 지정해야하는 예제를 생각할 수는 있지만 99 %의 99 %를 따르는 것은 매우 간단한 규칙입니다. 시각.
Baldrickk

@Baldrickk 때로는 다른 숫자 리터럴도 이름 뒤에 숨겨져서는 안됩니다.
중복 제거기

@ 중복 제거기 예제가 떠오 릅니까?
Baldrickk


18

코드에 왜 특정 리터럴 값이 포함되어 있습니까?

  • 이 값이 문제 영역 에서 특별한 의미가 있습니까?
  • 아니면이 값은 구현 세부 사항 입니까?이 값은 주변 코드의 직접적인 결과입니까?

리터럴 값이 문맥에서 명확하지 않은 의미를 갖는 경우 예, 상수 또는 변수를 통해 해당 값의 이름을 지정하면 도움이됩니다. 나중에 원래 컨텍스트를 잊었을 때 의미있는 변수 이름을 가진 코드를 유지 관리하기가 더 쉽습니다. 코드의 대상은 주로 컴파일러가 아니며 (컴파일러는 끔찍한 코드로 즐겁게 작업 할 것입니다.) 코드의 미래 관리자는 코드가 다소 자기 설명 적인지 잘 알고 있습니다.

  • 첫 번째 예에서와 같은 리터럴의 의미는 34, 4, 5문맥으로부터 명백하지 않다. 대신, 이러한 값 중 일부는 문제 영역에서 특별한 의미를 갖습니다. 그러므로 그들에게 이름을주는 것이 좋았습니다.

  • 두 번째 예에서 리터럴의 의미 1는 문맥에서 매우 분명합니다. 이름을 소개하는 것은 도움이되지 않습니다.

실제로, 실제 값을 숨기므로 명백한 값의 이름을 도입하는 것도 좋지 않을 수 있습니다.

  • 명명 된 값이 변경되거나 올바르지 않은 경우, 특히 관련이없는 코드에서 동일한 변수가 재사용되는 경우 버그가 사라질 수 있습니다.

  • 코드는 특정 값에 대해서는 잘 작동하지만 일반적인 경우에는 올바르지 않을 수 있습니다. 불필요한 추상화를 도입함으로써 코드는 더 이상 정확하지 않습니다.

"명백한"리터럴에는 크기 제한이 없습니다. 이는 컨텍스트에 따라 달라지기 때문입니다. 예를 들어 리터럴 1024은 파일 크기 계산 31의 컨텍스트에서, 또는 해시 함수 padding: 0.5em의 컨텍스트에서 또는 리터럴 이 CSS 스타일 시트 의 컨텍스트에서 완전히 명확 할 수 있습니다 .


8

이 코드 조각에는 몇 가지 문제가 있으며, 다음과 같이 단축 할 수 있습니다.

public List<Money> getMoneyByPersons() {
    return persons.size() == 1 ?
        moneyService.getMoneyIfHasOnePerson() :
        moneyService.getMoney(); 
}
  1. 한 사람이 왜 특별한 경우인지는 확실하지 않습니다. 한 사람의 돈을받는 것이 여러 사람의 돈을 얻는 것과는 다르다는 특정 비즈니스 규칙이 있다고 가정합니다. 그러나, 나는 가서 모두 내부 봐야 getMoneyIfHasOnePerson하고 getMoney이 뚜렷한 경우 이유를 이해하는 기대.

  2. 이름 getMoneyIfHasOnePerson이 맞지 않습니다. 이름에서 나는 한 사람이 있는지 확인하고,이 경우에는 그에게서 돈을 얻는 방법을 기대합니다. 그렇지 않으면 아무것도하지 마십시오. 코드에서 이것은 일어나고 있지 않습니다 (또는 조건을 두 번 수행하고 있습니다).

  3. List<Money>컬렉션 이 아닌 반품해야 할 이유가 있습니까?

귀하의 질문으로 돌아 가면, 왜 한 사람에게 특별한 대우가 있는지 확실하지 않기 때문에 규칙을 명시 적으로 표현하는 다른 방법이 없다면 숫자를 상수로 대체해야합니다 . 여기서 하나는 다른 마법 번호와 크게 다르지 않습니다. 특별 대우가 1 명, 2 명 또는 3 명 또는 12 명 이상에게만 적용된다는 비즈니스 규칙이있을 수 있습니다.

최상의 솔루션을 선택하기 위해 컨텍스트를 어떻게 구별 할 수 있습니까?

코드를보다 명확하게 만드는 것은 무엇이든합니다.

실시 예 1

다음 코드를 상상해보십시오.

if (sequence.size() == 0) {
    return null;
}

return this.processSequence(sequence);

여기서 0은 마법의 가치입니까? 코드는 다소 명확합니다. 시퀀스에 요소가 없으면 처리하지 말고 특수한 값을 반환하십시오. 그러나이 코드는 다음과 같이 다시 작성할 수도 있습니다.

if (sequence.isEmpty()) {
    return null;
}

return this.processSequence(sequence);

여기서는 더 이상 일정하지 않으며 코드가 더 명확합니다.

실시 예 2

다른 코드를 보자.

const result = Math.round(input * 1000) / 1000;

round(value, precision)과부하 가없는 JavaScript와 같은 언어에서 수행하는 작업을 이해하는 데 시간이 많이 걸리지 않습니다 .

이제 상수를 도입하려면 어떻게 호출합니까? 얻을 수있는 가장 가까운 용어는 Precision입니다. 그래서:

const precision = 1000;
const result = Math.round(input * precision) / precision;

가독성이 향상됩니까? 그것은 수 있습니다. 여기서 상수의 값은 다소 제한되어 있으므로 리팩토링을 실제로 수행해야하는지 스스로에게 물어볼 수 있습니다. 여기서 좋은 점은 이제 정밀도가 한 번만 선언되므로 변경하면 다음과 같은 실수를 할 위험이 없다는 것입니다.

const result = Math.round(input * 100) / 1000;

한 위치에서 값을 변경하고 다른 위치에서 값을 잊어 버립니다.

실시 예 3

이러한 예에서 숫자가 모든 경우 에 상수로 대체되어야한다는 인상을받을 수 있습니다 . 사실이 아닙니다. 경우에 따라 상수가 있다고해서 코드가 향상되지는 않습니다.

다음 코드를 보자.

class Point
{
    ...
    public void Reset()
    {
        x, y = (0, 0);
    }
}

0을 변수로 바꾸려고하면 의미있는 이름을 찾는 것이 어려울 것입니다. 이름을 어떻게 지정 하시겠습니까? ZeroPosition? Base? Default? 여기에 상수를 도입하면 코드가 향상되지 않습니다. 조금 더 길어질 것입니다.

그러나 그러한 경우는 드 rare니다. 따라서 코드에서 숫자를 찾을 때마다 코드를 리팩터링하는 방법을 찾으려고 노력하십시오. 숫자에 비즈니스 의미가 있는지 자문 해보십시오. 그렇다면 상수가 필수입니다. 아니면 번호를 어떻게 지정 하시겠습니까? 의미있는 이름을 찾으면 좋습니다. 그렇지 않은 경우 상수가 필요하지 않은 경우가 있습니다.


나는 3a를 추가 할 것이다 : 변수 이름, 사용 금액, 균형 등에 돈이라는 단어를 사용하지 마십시오. 돈 모음은 의미가 없으며, 금액이나 잔액 모음은 의미가 없습니다. (OK 나는 작은 외국 돈 (또는 동전)을 가지고 있지만 그것은 프로그래밍이 아닌 다른 문제입니다).
Bent

3

단일 매개 변수를 사용하고 4를 5로 나눈 값을 반환하여 첫 번째 예제를 사용하면서도 작업에 깨끗한 별칭을 제공하는 함수를 만들 수 있습니다.

나는 단지 내 자신의 평소 전략을 제공하고 있지만 아마도 무언가를 배울 것입니다.

내가 생각하는 것은

// Just an example name  
function normalize_task_value(task) {  
    return (task * 4) / 5;  // or to `return (task * 4) / NUMBER_OF_TASKS` but it really matters what logic you want to convey and there are reasons to many ways to make this logical. A comment would be the greatest improvement

}  

// Is it possible to just use tasks.length or something like that?  
// this NUMBER_OF_TASKS is the only one thats actually tricky to   
// understand right now how it plays its role.  

function normalize_all_task_values(tasks, accumulator) {  
    for (int i = 0; i < NUMBER_OF_TASKS; i++) {  
        accumulator += normalize_task_value(tasks[i]);
    }
}  

내가 기본이 아니지만 자바 스크립트 개발자 일 경우 미안합니다. 나는 이것이 어떤 구성을 정당화했는지 잘 모르겠습니다. 모든 것이 배열이나 목록에 있어야 할 필요는 없지만 .length는 많은 의미가 있다고 생각합니다. 그리고 * 4는 그 기원이 모호하기 때문에 좋은 것을 일정하게 만들 것입니다.


5
그러나 그 값 4와 5는 무엇을 의미합니까? 그 중 하나를 변경해야하는 경우 유사한 컨텍스트에서 번호가 사용되는 모든 장소를 찾고 그에 따라 해당 위치를 업데이트 할 수 있습니까? 이것이 원래 질문의 핵심입니다.
Bart van Ingen Schenau

나는 첫 번째 예가 꽤 당당한 자기 설명이므로 내가 대답 할 수없는 것이라고 생각합니다. 미래에 수술이 바뀌지 않아야합니다. 필요한 경우 필요한 것을 달성하기 위해 새로운 수술을 쓸 것입니다. 의견은이 목적에 가장 도움이 될 것이라고 생각합니다. 내 언어로 줄이려면 한 번만 전화하십시오. 평신도에게 절대적으로 이해하는 것이 얼마나 중요합니까?
etisdew

@BartvanIngenSchenau 전체 기능의 이름이 잘못 지정되었습니다. 더 나은 함수 이름, 함수가 반환 해야하는 사양이있는 주석 및 * 4/5가 달성하는 이유를 선호합니다. 상수 이름은 실제로 필요하지 않습니다.
gnasher729

@ gnasher729 : 잘 명명 된 잘 문서화 된 함수의 소스 코드에 상수가 정확히 한 번 나타나는 경우 명명 된 상수를 사용하지 않아도됩니다. 상수가 동일한 의미로 여러 번 나타나는 즉시 상수 이름을 지정하면 리터럴 (42)의 모든 인스턴스가 동일한 것을 의미하는지 여부를 알 필요가 없습니다.
Bart van Ingen Schenau

그 중심에 예를 들어 값을 변경해야 할 수도 있습니다. 그러나 변수를 나타내는 const로 변경합니다. 나는 그것이 사실이라고 생각하지 않으며 이것은 단순히 위의 범위에있는 중간 변수 여야합니다. 나는 모든 것을 함수에 매개 변수로 만들지 말고 싶지만 변경할 수는 있지만 거의 변경하지 않고 해당 함수 또는 객체 / 구조체 범위에 대한 내부 매개 변수로 만들려고합니다. 글로벌 범위 만.
etisdew

2

그 숫자 1, 다른 숫자 일 수 있습니까? 2 또는 3 일 수 있습니까, 아니면 1이어야하는 논리적 이유가 있습니까? 1이어야한다면 1을 사용하는 것이 좋습니다. 그렇지 않으면 상수를 정의 할 수 있습니다. 그 상수를 ONE이라고 부르지 마십시오. (나는 그것을 보았다).

분당 60 초-상수가 필요합니까? 음, 그것은 이다 60초,하지 (50) 또는 (70) 그리고 모두가 그것을 알고있다. 그래서 그것은 숫자를 유지할 수 있습니다.

페이지 당 60 개의 항목이 인쇄되었습니다.이 수는 59 또는 55 또는 70 일 수 있습니다. 실제로 글꼴 크기를 변경하면 55 또는 70이 될 수 있습니다. 따라서 여기에는 의미있는 상수가 더 필요합니다.

의미가 얼마나 명확한 지 또한 중요합니다. "분 = 초 / 60"이라고 쓰면 분명합니다. "x = y / 60"이라고 쓰면 명확하지 않습니다. 가 있어야합니다 몇 가지 의미있는 이름 어딘가에.

하나의 절대 규칙이 있습니다. 절대 규칙이 없습니다. 실제로는 숫자를 사용할 때와 명명 된 상수를 사용할 때를 알아냅니다. 책이 그렇게 말하는 이유를 이해하기 전까지는 그렇게하지 마십시오.


"1 분에 60 초 ... 그리고 모든 사람들이 그것을 알고 있습니다. 그래서 그것은 숫자를 유지할 수 있습니다." -나에게 유효한 주장이 아닙니다. "60"을 사용하는 코드에 200 자리가 있으면 어떻게합니까? 초에서 분으로 사용되는 장소와 다른 "똑같이"자연적으로 사용하는 장소를 어떻게 찾습니까? - 그러나 실제로 나도 사용 감히 minutes*60때로는, hours*3600내가 추가 상수를 선언하지 않고 그것을 필요로 할 때. 일 동안 아마 쓸 것 d*24*3600또는 d*24*60*60때문에이 86400가까운 사람을 한 눈에서 마법 번호를 인식하지 못합니다 가장자리입니다.
JimmyB

@JimmyB 코드의 200 개 장소에서 "60"을 사용하는 경우 솔루션에서 상수를 도입하지 않고 함수를 리팩토링 할 가능성이 큽니다.
klutt

0

DB 검색의 (수정 된) OP와 같이 상당한 양의 코드를 보았습니다. 쿼리는 목록을 반환하지만 비즈니스 규칙에 따르면 하나의 요소 만있을 수 있습니다. 그리고 물론, '이 경우 하나'에 대해 무언가가 하나 이상의 항목이있는 목록으로 변경되었습니다. (예, 나는 꽤 많은 시간을 말했다. 그것은 거의 ... nm와 같다)

따라서 상수를 작성하는 대신 (깨끗한 코드 메소드에서) 이름을 지정하거나 조건부에서 감지하려는 대상을 명확하게하는 메소드를 작성하고 감지하는 방식을 캡슐화합니다.

public int getMoneyByPersons(){
  if(isSingleDepartmentHead()){ 
    // TODO - return money for one person
  } else {
    // TODO - calculate and return money for people.
  }
}

public boolean isSingleDepartmentHead() {
   return persons.size() == 1;
}

취급을 통일하는 것이 훨씬 바람직합니다. n 개의 요소리스트는 n이 무엇이든 균일하게 처리 될 수 있습니다.
중복 제거기

나는 그것이 단일 사용자가 실제로 동일한 사용자가 n 크기 목록의 일부였던 경우와는 다른 (마진 적) 값이 아니라는 것을 보았습니다. 잘 수행 된 OO에서는 이것이 문제가되지 않지만 레거시 시스템을 다룰 때 좋은 OO 구현이 항상 실현 가능한 것은 아닙니다. 우리가 얻은 최고는 DAO 논리에 대한 합리적인 OO 외관이었습니다.
Kristian H
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.