상사는 작은 함수 작성을 중단하고 동일한 루프에서 모든 작업을 수행하도록 요청합니다.


209

Clean Code 라는 책을 읽었습니다.Robert C. Martin의 . 이 책에서는 작은 함수 작성, 이름 선택 등의 코드를 정리하는 많은 방법을 보았습니다. 필자가 읽은 깨끗한 코드에 관해 가장 흥미로운 책으로 보입니다. 그러나 오늘 상사는이 책을 읽은 후 코드를 작성하는 방식을 좋아하지 않았습니다.

그의 주장은

  • 작은 함수를 작성하면 코드가 무엇을하는지보기 위해 각각의 작은 함수로 이동해야하기 때문에 고통 스럽다.
  • 메인 루프가 300 줄 이상이더라도 모든 것을 메인 큰 루프에 넣으십시오. 읽기가 더 빠릅니다.
  • 코드를 복제해야하는 경우 작은 함수 만 작성하십시오.
  • 주석의 이름으로 함수를 작성하지 말고 복잡한 주석 코드 (3-4 행)를 위의 주석으로 작성하십시오. 마찬가지로 실패한 코드를 직접 수정할 수 있습니다

이것은 내가 읽은 모든 것에 위배됩니다. 일반적으로 코드는 어떻게 작성합니까? 하나의 주요 큰 루프, 작은 기능 없음?

내가 사용하는 언어는 주로 자바 스크립트입니다. 명확하게 명명 된 작은 모든 기능을 삭제하고 모든 것을 큰 루프에 넣었으므로 지금 읽기가 어렵습니다. 그러나 상사는 이것을 좋아합니다.

한 예는 다음과 같습니다.

// The way I would write it
if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

// The way he would write it
// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

예를 들어 읽은 책에서 주석은 작은 함수를 작성하고 업데이트되지 않은 주석 (주석이 아닌 코드를 수정)으로 이어지는 경우 쓸모가 없기 때문에 깨끗한 코드를 작성하지 못하는 것으로 간주됩니다. 그러나 내가하는 일은 주석을 삭제하고 주석 이름으로 함수를 작성하는 것입니다.

글쎄, 조언을하고 싶습니다. 깨끗한 코드를 작성하는 것이 어떤 방법 / 연습이 더 낫습니까?



4
phoneNumber = headers.resourceId? : DEV_PHONE_NUMBER;
Joshua

10
경영진이 해결해야 할 작업 대신 작업을 수행하는 방법을 알려주는 작업을 수행하고 있는지 확인하십시오.
Konstantin Petrukhnov

8
@rjmunro 당신이 정말로 당신의 직업을 좋아하지 않는다면, 직업보다 개발자가 적다는 것을 명심하십시오. Martin Fowler의 말 : "조직을 바꿀 수 없다면 조직을 바꾸십시오!" 코딩하는 방법을 알려주는 보스는 변경하고 싶은 조언입니다.
Niels van Reijmersdal

10
isApplicationInProduction()기능 이 없습니다 ! 테스트를 수행해야하며 코드가 프로덕션 환경과 다른 방식으로 작동하는 경우 테스트가 쓸모가 없습니다. 프로덕션 환경에서 의도적 으로 테스트되지 않은 코드를 발견하는 것과 같습니다 .
Ronan Paixão

답변:


215

먼저 코드 예제를 보자. 당신은 선호합니다 :

if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

그리고 당신의 상사는 그것을 다음과 같이 쓸 것입니다 :

// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

제 생각에는 둘 다 문제가 있습니다. 내가 당신의 코드를 읽을 때, 나의 즉각적인 생각은 "당신은 그것을 if삼항식으로 바꿀 수 있습니다 "였습니다. 그런 다음 상사의 코드를 읽고 "그가 왜 당신의 기능을 주석으로 대체 했습니까?"라고 생각했습니다.

최적의 코드가 둘 사이에 있다고 제안합니다.

phoneNumber = isApplicationInProduction(headers) ? headers.resourceId : DEV_PHONE_NUMBER;

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

이는 단순화 된 테스트 표현과 주석이 테스트 가능한 코드로 대체되는 두 가지 이점을 모두 제공합니다.

코드 디자인에 대한 상사의 관점과 관련하여 :

작은 함수를 작성하면 코드가 무엇을하는지보기 위해 각 작은 함수로 이동해야하기 때문에 어려움이 있습니다.

함수의 이름이 잘 정해져 있다면 그렇지 않습니다. isApplicationInProduction자명하며 코드를 검사하여 코드의 기능을 확인할 필요는 없습니다. 실제로 반대의 경우도 마찬가지입니다. 코드를 살펴보면 함수 이름보다 의도가 적습니다 (따라서 상사가 주석에 의존해야합니다).

메인 루프가 300 줄 이상이더라도 모든 것을 메인 큰 루프에 넣으십시오. 읽기가 더 빠릅니다.

스캔하는 것이 더 빠를 수 있지만 실제로 코드를 "읽기"위해서는 머리에서 효과적으로 실행할 수 있어야합니다. 작은 기능으로는 쉽고 100 줄 길이의 방법으로는 실제로 어렵습니다.

코드를 복제해야하는 경우 작은 함수 만 작성

동의하지 않습니다. 코드 예제에서 알 수 있듯이 이름이 작고 이름이 작은 함수는 코드의 가독성을 향상 시키며, 예를 들어 "어떻게"에 관심이없고 기능의 "무엇"에만 관심이있을 때마다 사용해야합니다.

주석의 이름으로 함수를 작성하지 말고 위의 주석과 함께 복잡한 코드 줄 (3-4 행)을 넣으십시오. 이와 같이 실패한 코드를 직접 수정할 수 있습니다

나는 이것이 진지하다고 가정 할 때, 이것에 대한 추론을 정말로 이해할 수 없다. The Expert Beginner 트위터 계정에 의해 패러디로 작성된 것으로 예상되는 종류입니다 . 주석에는 근본적인 결함이 있습니다. 주석은 컴파일 / 해석되지 않으므로 단위 테스트를 할 수 없습니다. 코드가 수정되고 주석이 홀로 남게되어 어느 쪽이 옳은지 알 수 없게됩니다.

자체 문서화 코드를 작성하는 것은 어렵고 보충 문서 (주석 형태로도)가 필요한 경우가 있습니다. 그러나 주석이 코딩 실패라는 "Uncle Bob"의 견해는 너무나 자주 적용됩니다.

상사에게 Clean Code 책을 읽게하고 코드를 만족시키기 위해 코드를 읽기 어렵게 만들지 않도록하십시오. 궁극적으로, 당신이 그를 바꾸도록 설득 할 수 없다면, 줄을 서거나 더 나은 코딩을 할 수있는 새로운 보스를 찾아야합니다.


26
작은 기능도
쉽게

13
Quoth @ ExpertBeginner1 :“코드의 어느 곳 에서나 많은 작은 메소드를 보는 것이 지겨워서 앞으로는 모든 메소드에서 최소 15 LOC가 있습니다.”
Greg Bacon

34
"의견은 근본적인 결함이있다 : 그것들은 컴파일 / 해석되지 않았으므로 단위 테스트를 할 수 없다"악마의 옹호자를 여기에서 칭찬하면, "의견"을 "함수 이름"으로 대체해도 마찬가지이다.
mattecapu

11
@mattecapu, 나는 당신의 옹호를 취하고 그것을 다시 두 배로 할 것입니다. 모든 오래된 쓰레기 개발자는 코드 조각의 기능을 설명하려는 주석으로 와플 수 있습니다. 좋은 기능 명을 가진 코드를 간결하게 설명하는 데는 숙련 된 의사 소통자가 필요합니다. 최고의 개발자는 코드 작성이 주로 다른 개발자와 통신하는 데 관심이 있고 컴파일러를 보조 문제로 사용하므로 숙련 된 의사 소통 자입니다. Ergo, 훌륭한 개발자는 잘 명명 된 함수를 사용하고 주석은 사용하지 않습니다. 열악한 개발자는 자신의 기술을 주석 사용에 대한 변명 뒤에 숨 깁니다.
David Arno

4
@DavidArno 모든 기능에는 사전 및 사후 조건이 있으므로 문제는 문서화 여부입니다. 함수가 측정 된 피트 단위의 거리 인 매개 변수를 사용하는 경우 킬로미터가 아닌 피트 단위로 제공해야합니다. 이것은 전제 조건입니다.
Jørgen Fogh

223

다른 문제가 있습니다

기본적으로 디버그 테스트 사례로 코드를 부풀리기 때문에 어느 쪽 코드도 좋지 않습니다 . 어떤 이유에서든 더 많은 것을 테스트하려면 어떻게해야합니까?

phoneNumber = DEV_PHONE_NUMBER_WHICH_CAUSED_PROBLEMS_FOR_CUSTOMERS;

또는

phoneNumber = DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY;

더 많은 지점을 추가 하시겠습니까?

중요한 문제는 기본적으로 코드의 일부를 복제하므로 실제 코드를 실제로 테스트하지는 않는다는 것입니다. 디버그 코드를 작성하여 디버그 코드를 테스트하지만 프로덕션 코드는 테스트하지 않습니다. 병렬 코드베이스를 부분적으로 만드는 것과 같습니다.

나쁜 코드를 더 영리하게 작성하는 방법에 대해 상사와 논쟁하고 있습니다. 대신 코드 자체의 고유 한 문제를 해결해야합니다.

의존성 주입

코드는 다음과 같습니다.

phoneNumber = headers.resourceId;

여기에는 논리에 분기가 없기 때문에 분기가 없습니다. 프로그램은 헤더에서 전화 번호를 가져와야합니다. 기간.

DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY결과적으로 원한다면에 넣어야합니다 headers.resourceId. 이를 수행하는 한 가지 방법은 headers테스트 사례에 다른 객체 를 단순히 주입하는 것입니다 (이 코드가 적절하지 않으면 죄송합니다. JavaScript 기술은 약간 녹슨 것입니다).

function foo(headers){
    phoneNumber = headers.resourceId;
}

// Creating the test case
foo({resourceId: DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY});

headers이것이 서버로부터 수신 한 응답의 일부 라고 가정 : 이상적으로 headers는 테스트 목적으로 다양한 종류를 제공하는 전체 테스트 서버가 있습니다. 이렇게하면 실제 프로덕션 코드를있는 그대로 테스트하고 프로덕션 코드처럼 작동하거나 작동하지 않을 수있는 절반의 중복 된 코드 는 테스트 하지 않습니다.


11
나는 내 자신의 대답 으로이 주제를 다루는 것을 고려했지만 이미 충분히 길 었다고 느꼈습니다. 그렇게하기 위해 당신에게 +1 :)
David Arno

5
@DavidArno 나는 그것을 처음 읽을 때 질문이 여전히 잠겨 있기 때문에 답에 주석으로 추가하려고했습니다. 다시 열리고 놀랍게도 이것을 답변으로 추가했습니다. 자동화 된 테스트를 수행하기위한 수십 개의 프레임 워크 / 도구가있을 수 있습니다. 특히 JS에서는 매일 새로운 것이 나오는 것 같습니다. 그래도 보스에게 팔기 어려울 수 있습니다.
null

56
@DavidArno 어쩌면 답을 더 작은 답으로 나누었을 것입니다. ;)
krillgar

2
@ user949300 비트 OR을 사용하는 것은 현명하지 않습니다;)
curiousdannii

1
@curiousdannii 그래, 편집하기에는 너무 늦었다는 것을 알아 차렸다.
user949300

59

이에 대한 "올바른"또는 "잘못된"대답은 없습니다. 그러나 소프트웨어 시스템을 설계하고 개발하는 36 년의 전문적인 경험을 바탕으로 의견을 제시 할 것입니다 ...

  1. "자체 문서화 코드"와 같은 것은 없습니다. 왜? 그 주장은 완전히 주관적입니다.
  2. 코멘트는 결코 실패가 아닙니다. 무엇 이며 실패하는 것은 전혀 이해할 수없는 코드 없이 의견.
  3. 하나의 코드 블록에있는 300 개의 중단없는 코드 라인은 유지 관리의 악몽이며 오류가 발생하기 쉽습니다. 이러한 블록은 잘못된 설계 및 계획을 나타냅니다.

당신이 제공 한 예제에 직접 말하기 ... isApplicationInProduction()자신의 일상에 두는 것이 현명한 일입니다. 오늘날이 테스트는 단순히 "헤더"에 대한 점검이며 삼항 ( ?:) 연산자 로 처리 할 수 ​​있습니다 . 내일, 시험은 훨씬 더 복잡 할 수 있습니다. 또한 "headers.resourceId"는 응용 프로그램의 "생산 상태"와 명확한 관계가 없습니다. 나는 그런 상태에 대한 테스트가 주장 할 필요가 기본 데이터에서 분리 될; 서브 루틴은이 작업을 수행하고 3 진은 수행하지 않습니다. 또한 resourceId가 "생산 중"테스트 인 이유에 대한 유용한 의견이 있습니다.

"명백하게 작게 명명 된 작은 기능들"로 함부로 넘어 가지 않도록주의하십시오. 루틴은 아이디어를 "단순한 코드"보다 캡슐화해야합니다. 나는 "생산 상태"테스트를 수행해야하는 amon의 제안을지지 phoneNumber = getPhoneNumber(headers)하고 추가합니다.getPhoneNumber()isApplicationInProduction()


25
실패가 아닌 좋은 의견 과 같은 것이 있습니다. 그러나 설명하는 코드와 거의 같은 주석은 메소드 / 클래스 등의 앞의 빈 주석 블록입니다. 확실히 실패입니다.
jpmc26

3
코드가 수행하는 작업과 처리하는 경우와 처리하지 않는 경우에 대한 영어 설명보다 작고 읽기 쉬운 코드를 가질 수 있습니다. 또한 함수가 자체 메소드로 추출되면 함수를 읽는 사람은 코너 사례가 무엇인지 또는 호출자가 처리하지 않는지 알 수 없으며 함수 이름이 매우 장황하지 않으면 호출자를 검사하는 사람이 코너를 모를 수 있습니다 케이스는 함수에 의해 처리됩니다.
supercat

7
주석은 본질적으로 실패 하지 않습니다 . 주석은 실패 일 수 있으며 정확하지 않은 경우에도 마찬가지입니다. 블랙 박스 모드에서의 잘못된 동작을 포함하여 여러 수준에서 잘못된 코드가 감지 될 수 있습니다. 잘못된 설명은 두 가지 모델이 설명되어 있고 그 중 하나가 잘못되었다는 인식을 통해 화이트 박스 모드에서 사람이 이해하는 것으로 만 탐지 할 수 있습니다.
Timbo

7
@Timbo "... 적어도 하나 라도 잘못되었습니다." ;)
jpmc26

5
당신이 이해 할 수없는 경우 @immibis 어떤 코드가 주석을 사용하지 않을 때, 다음 코드는 아마 충분히 명확하지 않습니다. 주석의 주요 목적 은 코드가 수행하는 작업을 수행 하는 이유를 명확 하게 하는 것입니다. 미래의 관리자에게 그의 디자인을 설명하는 코더입니다. 코드는 그런 종류의 설명을 제공 할 수 없으므로 주석은 이해의 차이를 채 웁니다.
Graham

47

"필수 이상의 요소를 곱하면 안됩니다."

— 오캄의 면도기

코드는 가능한 간단해야합니다. 버그는 복잡성을 발견하기가 어렵 기 때문에 복잡성 사이에 숨는 것을 좋아합니다. 그렇다면 코드를 간단하게 만드는 것은 무엇입니까?

작은 단위 (파일, 함수, 클래스) 는 좋은 생각 입니다. 작은 단위는 한 번에 이해해야 할 것이 적기 때문에 이해하기 쉽습니다. 정상적인 인간은 한 번에 ~ 7 개의 개념 만 저글링 할 수 있습니다. 그러나 크기는 코드 줄로 측정되지 않습니다. . 코드를 "골프"(짧은 변수 이름 선택, "영리한"바로 가기 사용, 가능한 한 많은 코드를 한 줄로 스매싱)하면 가능한 적은 코드를 작성할 수 있지만 최종 결과는 간단하지 않습니다. 이러한 코드를 이해하려는 것은 읽는 것보다 리버스 엔지니어링과 비슷합니다.

함수를 줄이는 한 가지 방법은 다양한 도우미 함수를 추출하는 것입니다. 자체 포함 된 복잡성을 추출 할 때 좋은 아이디어가 될 수 있습니다 . 이와 관련하여 복잡성 문제는 관련없는 문제에 포함 된 경우보다 관리 및 테스트가 훨씬 간단합니다.

그러나 모든 함수 호출에는 인지 오버 헤드가 있습니다 . 현재 코드 에서 코드를 이해해야 할뿐만 아니라 외부 코드와 상호 작용하는 방식도 이해해야합니다 . 추출한 함수가 추출하는 것보다 함수에 더 많은 복잡성을 도입 한다고 말하는 것이 공정하다고 생각합니다 . “ 작은 함수는 코드가하는 일을보기 위해 각각의 작은 함수로 이동하도록 강요하기 때문에 상사가 의미하는 바 는 고통입니다. "

때로는 긴 보링 기능 이 수백 줄 길이 인 경우에도 이해하기 가 매우 간단 할 수 있습니다 . 예를 들어 드래그 앤 드롭 편집기없이 GUI를 직접 만들 때 초기화 및 구성 코드에서 발생하는 경향이 있습니다. 합리적으로 추출 할 수있는 자체 포함 된 복잡성은 없습니다. 그러나 서식을 읽을 수 있고 주석이 있으면 실제로 일어나는 일을 따르는 것이 어렵지 않습니다.

다른 많은 복잡성 메트릭이 있습니다. 범위의 변수 수는 가능한 작아야합니다. 그렇다고 변수 를 피해야 한다는 의미는 아닙니다 . 즉, 각 변수를 필요한 가장 작은 범위로 제한해야합니다. 변수에 포함 된 값을 변경하지 않으면 변수가 더 단순 해집니다.

매우 중요한 지표는 순환 복잡성 (McCabe 복잡성)입니다. 코드 조각을 통한 독립 경로 수를 측정합니다. 이 수는 각 조건에 따라 지수 적 으로 증가 합니다. 각 조건부 또는 루프는 경로 수를 두 배로 늘립니다. 10 점 이상의 점수가 너무 복잡하다는 증거가 있습니다. 이는 점수가 5 인 매우 긴 함수가 점수가 25 인 매우 짧고 밀도가 높은 함수보다 낫다는 것을 의미합니다. 제어 흐름을 별도의 함수로 추출하여 복잡성을 줄일 수 있습니다.

조건은 완전히 추출 할 수있는 복잡한 부분의 예입니다.

function bigFatFunction(...) {
  ...
  phoneNumber = getPhoneNumber(headers);
  ...
}

...

function getPhoneNumber(headers) {
  return headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;
}

이것은 여전히 ​​유용한 가장자리에 있습니다. 이 조건이 매우 조건 적이 지 않기 때문에 복잡성 크게 줄어 듭니다 . 프로덕션 환경에서는 항상 같은 경로를 사용합니다.


복잡성은 결코 사라질 수 없습니다. 그것은 단지 뒤섞 일 수 있습니다. 많은 작은 것들이 작은 것보다 간단합니까? 상황에 따라 크게 달라집니다. 보통, 기분이 좋은 조합이 있습니다. 다양한 복잡성 요소 사이의 타협점을 찾으려면 직관과 경험, 약간의 행운이 필요합니다.

아주 작은 함수와 아주 간단한 함수를 작성하는 방법을 아는 것은 대안을 모른 채 선택할 수 없기 때문에 유용한 기술입니다. 현재 상황에 어떻게 적용되는지에 대해 생각하지 않고 규칙이나 모범 사례맹목적으로 따르는 것은 최악의 경우 최상의화물 컬트 프로그래밍에서 평균 결과를 도출 합니다.

내가 당신의 상사와 동의하지 않는 곳입니다. 그의 주장은 유효하지 않지만 Clean Code 서적도 틀리다. 아마도 상사의 지침을 따르는 것이 낫지 만, 더 나은 방법을 찾으려고 노력하면서 이러한 문제에 대해 생각하고 있다는 사실은 매우 유망합니다. 경험을 쌓으면 코드에 대한 적절한 팩토링을 쉽게 찾을 수 있습니다.

(참고 :이 답변은 The Whiteboard by Jimmy HoffaReasonable Code 블로그 게시물 의 생각을 바탕으로 작성되었으며 코드를 간단하게 만드는 것에 대한 높은 수준의 견해를 제공합니다.)


나는 일반입니다 나는 당신의 응답을 좋아했습니다. 그러나 mcabes 순환 복잡도 측정에는 문제가 있습니다. 내가 본 것에서, 그것은 복잡성의 진정한 척도를 제시하지 않습니다.
Robert Baron

27

Robert Martin의 프로그래밍 스타일은 극화되고 있습니다. "많이"나누는 것이 너무 많은 이유와 함수를 조금 더 크게 유지하는 것이 "더 나은 방법"인 이유를 많이 발견 한 많은 프로그래머, 경험이 풍부한 프로그래머도 있습니다. 그러나 이러한 "논쟁"의 대부분은 종종 오래된 습관을 바꾸고 새로운 것을 배우려는 의지가없는 표현입니다.

듣지 마!

코드 이름을 표현 이름이있는 별도의 함수로 리팩토링하여 주석을 저장할 수있을 때마다 코드를 개선하십시오. Bob Martin이 깨끗한 코드북에서하는 것만 큼은 아니지만, 과거에 보았던 대부분의 코드는 유지 보수 문제로 인해 너무 작은 기능이 아닌 너무 큰 기능을 포함했습니다. 따라서 자체 설명 이름으로 작은 함수를 작성하려고 시도하는 것이 좋습니다.

자동 리팩토링 도구를 사용하면 분석법을 쉽고 간단하고 안전하게 추출 할 수 있습니다. 그리고 300 줄 이상의 함수 작성을 권장하는 사람들을 진지하게 생각하지 마십시오. 그러한 사람들은 코드 작성 방법을 말할 자격이 없습니다.


53
"그 말을 듣지 마라!" : 영업 이익이 요구된다는 사실 주어진 자신의 상사에 의해 분할 코드를 중단, 영업 이익은 아마 당신의 조언을 피해야한다. 상사가 자신의 오래된 습관을 바꾸고 싶지 않더라도. 또한 이전 답변에서 강조한 것처럼 OP 코드와 보스 코드는 잘못 작성되었으며 의도적으로 또는 그렇지 않은 경우 답변에 언급하지 않았습니다.
Arseni Mourzenko

10
@ArseniMourzenko : 우리 모두가 그의 상사보다 먼저 버클을 달릴 필요는 없습니다. OP가 올바른 일을해야 할 때나 상사가 말한 것을해야 할 때를 알 수있을만큼 오래 되었기를 바랍니다. 그리고 예, 의도적으로 예제의 세부 사항에 들어 가지 않았습니다. 이러한 세부 사항에 대해 이미 다른 답변이 충분합니다.
Doc Brown

8
@DocBrown 합의. 전체 학급에서 300 줄은 의심 스럽다. 300 라인 기능은 음란합니다.
JimmyJames

30
나는 완벽하게 좋은 수업 인 300 줄 이상의 길이의 많은 수업을 보았습니다. Java는 매우 장황하므로 코드가 많지 않으면 클래스에서 의미있는 것을 거의 할 수 없습니다. 따라서 "클래스의 코드 줄 수"는 그 자체로는 의미있는 지표가 아니며, SLOC를 프로그래머의 생산성에 대한 의미있는 지표로 간주하는 것 이상입니다.
Robert Harvey

9
또한 밥 삼촌의 현자 조언이 잘못 해석되어 남용되어 많은 경험이있는 프로그래머 에게는 유용 할 것이라는 의심이 들었습니다 .
Robert Harvey

23

귀하의 경우 : 전화 번호를 원합니다. 전화 번호를 얻는 방법이 분명한 경우 명확한 코드를 작성하십시오. 또는 전화 번호를 얻는 방법이 확실하지 않은 경우 방법을 작성하십시오.

귀하의 경우 전화 번호를 얻는 방법이 명확하지 않으므로 방법을 작성하십시오. 구현은 분명하지 않지만 별도의 메소드에 배치하므로 한 번만 처리해야합니다. 구현이 명확하지 않기 때문에 주석이 유용합니다.

"isApplicationInProduction"메소드는 의미가 없습니다. getPhonenumber 메소드에서 호출하면 구현이 더 명확하지 않으며 진행 상황을 파악하기가 더 어려워집니다.

작은 기능을 쓰지 마십시오. 잘 정의 된 목적을 가지고 그 정의 된 목적을 충족시키는 함수를 작성하십시오.

추신. 나는 구현을 전혀 좋아하지 않는다. 전화 번호가 없으면 개발자 버전임을 의미합니다. 따라서 전화 번호가 프로덕션 환경에없는 경우 처리하지 않고 임의의 전화 번호로 대체하십시오. 고객이 10,000 명이고 전화 번호가 17 명인데 생산에 어려움을 겪고 있다고 상상해보십시오. 프로덕션 또는 개발 중인지 여부는 다른 것이 아닌 직접 확인해야합니다.


1
"작은 함수를 작성하지 마십시오. 잘 정의 된 목적을 가지고 정의 된 목적을 충족시키는 함수를 작성하십시오." 이것이 코드 분할에 대한 올바른 기준입니다. 함수가 다른 함수를 여러 개 (예를 들어 둘 이상) 너무 많이 수행 하면 분할합니다. 단일 책임 원칙 이 기본 원칙입니다.
robert bristow-johnson

16

어느 구현도 그다지 좋지 않다는 사실을 무시하더라도, 이것은 적어도 일회용 사소한 기능을 추상화하는 수준에서 본질적으로 맛의 문제라는 점에 주목할 것입니다.

대부분의 경우 줄 수는 유용한 지표가 아닙니다.

완전히 사소한 순차 순차 코드 (설정 또는 이와 유사한 것) 300 줄 (또는 3000 줄)은 거의 문제가되지 않지만 (하지만 자동 생성이나 데이터 테이블 등으로 더 나을 수 있음) 복잡한 100 줄의 중첩 루프 100 줄 가우시안 제거 또는 행렬 반전에서 찾을 수있는 종료 조건 및 수학은 너무 쉽게 따라갈 수 없습니다.

나를 위해, 일을 선언하는 데 필요한 코드의 양이 구현을 구성하는 코드의 양보다 훨씬 작지 않으면 단일 사용 함수를 작성하지 않을 것입니다 (결함 주입을 쉽게 수행 할 수 있기를 원한다고 말하지 않는 한). 이 조건에는 단일 조건부가 거의 없습니다.

이제는 작은 코어 임베디드 세계에서 왔으며 스택 깊이 및 호출 / 반환 오버 헤드와 같은 것을 고려해야합니다 (여기서 주장하는 것처럼 보이는 작은 함수에 대해 다시 논쟁하지만). 코드 검토에서 원래의 기능을 보았을 때 이전 스타일의 유즈넷 화염을 얻었습니다.

맛은 디자인이 가르치기가 어렵고 실제로 경험이 제공되는 것입니다. 함수 길이에 대한 규칙으로 줄일 수 있는지 확실하지 않으며 순환 적 복잡성조차도 메트릭으로 한계가 있습니다 (때로는 복잡하지만 태클하는 경우가 있습니다).
이것은 깨끗한 코드가 좋은 것들에 대해 논의하지 않는다는 것을 말하는 것이 아니며, 이러한 것들에 대해서는 현지 관습뿐만 아니라 기존의 코드베이스가 수행하는 것에 대해서도 고려해야합니다.

이 특정 예제는 사소한 세부 사항으로 선택하는 것 같습니다. 시스템을 쉽게 이해하고 디버깅하는 데 훨씬 더 중요하므로 훨씬 높은 수준의 항목에 더 관심이 있습니다.


1
나는 그것을 강력하게 동의합니다 . 함수로 감싸는 것을 고려하기 위해서는 매우 복잡한 원 라이너가 필요합니다 ... 나는 삼항 / 기본값 줄을 감싸지 않을 것입니다. 나는 하나의 라이너를 감쌌지만 일반적으로 파이프를 열 파이프가 10 개이며 코드를 실행하지 않고 이해할 수없는 쉘 스크립트입니다.
TemporalWolf

15

하나의 큰 루프에 모든 것을 넣지 말고 너무 자주하지 마십시오.

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

큰 루프의 문제점은 많은 화면에 걸쳐있을 때 전체 구조를보기가 어렵다는 것입니다. 따라서 단일 청크가 있고 재사용 가능한 큰 청크를 제거하십시오.

위의 작은 기능의 문제점은 원자 성과 모듈성이 일반적으로 좋지만 너무 멀리 갈 수 있다는 것입니다. 위 함수를 재사용하지 않으면 코드 가독성과 유지 관리 성이 떨어집니다. 세부 사항을 드릴 다운하려면 세부 사항을 인라인으로 읽을 수있는 대신 함수로 이동해야하며 함수 호출은 세부 사항보다 공간을 거의 차지하지 않습니다.

너무 많은 방법과 너무 적은 방법 사이에는 분명 균형이 있습니다 . 하나 이상의 장소에서 호출되지 않는 한 위와 같은 작은 함수를 절대로 나눌 수 없으며 심지어 새로운 논리를 도입하는 측면에서 기능 이 그다지 중요하지 않기 때문에 두 번 생각할 것입니다. 그런 간신히 자신의 존재를 보증합니다.


2
하나의 부울 하나의 라이너는 읽기 쉽지만 그 자체만으로 "무엇"이 발생하는지 설명합니다. 함수 이름이 "Why"이유를 설명하는 데 도움이되기 때문에 간단한 삼항 식을 래핑하는 함수를 계속 작성합니다.이 조건 검사를 수행하고 있습니다. 이는 특히 새로운 사람 (또는 6 개월 만에)이 비즈니스 논리를 이해해야 할 때 유용합니다.
AJ X.

14

실제로 원하는 것은 다음과 같습니다.

phoneNumber = headers.resourceId || DEV_PHONE_NUMBER

설정 :이은 설명을 읽는 사람에게해야 phoneNumber받는 사람 resourceId에 그것을 사용할 수, 또는 기본 경우 DEV_PHONE_NUMBER그렇지 않은 경우.

당신이 경우 진정으로 만 생산이 변수를 설정하려면, 당신은 당신이에서 실행중인 위치를 확인하기 위해 다른, 더 표준 응용 프로그램 전체의 유틸리티 메소드를 (매개 변수를 필요로하지 않는)가 있어야합니다. 해당 정보의 헤더를 읽는 것은 의미가 없습니다.


사용하는 언어에 대한 설명이 필요하지만 (어떤 언어를 사용하고 있는지 추측 할 수는 있지만) 어떤 일이 일어나고 있는지는 분명하지 않습니다. 개발자는 phoneNumber가 프로덕션 버전의 "resourceId"에 저장되어 있고 resourceId가 개발 버전에없고 개발 버전에 DEV_PHONE_NUMBER를 사용하려고한다고 가정합니다. 즉, 전화 번호가 이상한 곳에 저장됩니다. 제목은 프로덕션 버전에서 전화 번호가 누락되면 상황이 잘못 될 수 있음을 의미합니다
gnasher729

14

무뚝뚝하게 보자 : 귀하의 환경 (언어 / 프레임 워크 / 클래스 디자인 등)이 실제로 "깨끗한"코드에 적합하지 않은 것 같습니다. 당신은 가능한 한 모든 종류의 것들을 실제로 서로 가까이해서는 안되는 몇 줄의 코드로 혼합하고 있습니다. 단일 기능이 resourceId==undef프로덕션에 있지 않거나 프로덕션 이외의 시스템에서 기본 전화 번호를 사용하고 resourceId가 일부 "헤더"등에 저장되어 있다는 것을 알면 어떤 기능이 있습니까? headersHTTP 헤더 라고 가정 하므로 최종 사용자에게 어떤 환경에 대한 결정을 내릴 수 있습니까?

단일 부분을 함수로 분해하면 근본적인 문제에 큰 도움이되지 않습니다.

찾아야 할 키워드 :

  • 디커플링
  • 응집력
  • 의존성 주입

코드의 책임을 바꾸고 최신 프레임 워크 (환경 / 프로그래밍 언어에 존재하거나 존재하지 않을 수 있음)를 사용하여 코드를 전혀 사용하지 않고 원하는 (다른 상황에서) 원하는 것을 달성 할 수 있습니다.

설명 ( "주요 함수에서 300 줄의 코드")에서 "방법"대신 "함수"라는 단어조차도 달성하려는 목표에 아무런 의미가 없다고 가정합니다. 구식 프로그래밍 환경 (즉, 구조가 거의없고 의미있는 클래스가없고 MVC와 같은 클래스 프레임 워크 패턴이없는 기본 명령형 프로그래밍)에서는 실제로 아무 것도 할 점이 없습니다 . 근본적인 변경 없이는 섬프에서 벗어날 수 없습니다. 적어도 상사는 코드 복제를 피하기 위해 함수를 만들 수있는 것처럼 보입니다. 첫 번째 단계입니다!

나는 당신이 잘 설명하고있는 프로그래머의 유형뿐만 아니라 코드의 유형도 알고있다. 솔직히 동료라면 제 조언은 달라졌을 것입니다. 그러나 당신의 상사이기 때문에 당신이 이것에 대해 싸우는 것은 쓸모가 없습니다. 당신의 상사가 당신을 지배 할 수있을뿐만 아니라, 당신이 부분적으로 "당신의 일"을하고 당신의 상사 (그리고 아마도 다른 사람들)가 이전과 같이 계속한다면 코드 추가는 실제로 코드를 악화시킬 것입니다. 프로그래밍 스타일에 적응하고 (물론이 특정 코드베이스에서 작업하는 동안에 만) 최종 결과에 더 좋을 수도 있습니다.이 맥락에서 최선을 다해보십시오.


1
여기에는 분리해야 할 암시 적 구성 요소가 있지만 언어 / 프레임 워크에 대해 더 많이 알지 못하면 OO 접근 방식이 적합한 지 여부를 100 % 동의합니다. 분리 및 단일 책임 원칙은 순수한 기능 (예 : Haskell)에서 순수한 명령 (예 : C)에 이르기까지 모든 언어에서 중요합니다. 보스가 허용하는 경우 첫 번째 단계는 주 기능을 발송자 기능으로 변환하는 것입니다 ( 선언적 스타일 (알고리즘이 아닌 정책 설명)로 읽고 작업을 다른 기능에 적용하는 개요 또는 목차와 같은).
David Leppik

JavaScript는 일류 기능을 갖춘 프로토 타입입니다. 기본적으로 OO이지만 고전적인 의미는 아니므로 가정이 정확하지 않을 수 있습니다. YouTube에서 Crockford 동영상을 보는 큐 시간 ...
Kevin_Kinsey 19시 59 분

13

"청결"은 코드 작성의 한 가지 목표입니다. 유일한 목표는 아닙니다. 또 다른 가치있는 목표는 공동성 입니다. 비공식적으로 말하면, colocality는 코드를 이해하려는 사람들이 자신이하는 일을보기 위해 모든 곳을 뛰어 넘을 필요가 없음을 의미합니다. 삼항식 대신 잘 명명 된 함수를 사용하는 것이 좋은 것처럼 보일 수 있지만, 그러한 함수의 수와 위치에 따라이 연습이 성 가실 수 있습니다. 만약 사람들이 불평을한다면, 특히 사람들이 당신의 고용 상태에 대해 말하는 경우 들어야한다는 것을 제외하고는 당신이 그 선을 넘 었는지 말할 수 없습니다.


2
"... 사람들이 불평을한다면, 특히 사람들이 당신의 고용 상태에 대해 말을한다면 들어 봐야합니다". IMO 이것은 정말 나쁜 조언입니다. 당신이 얻을 수있는 직업을 고맙게 생각해야하는 심각하게 가난한 개발자가 아니라면, 항상 "직업을 바꿀 수 없다면, 직업을 바꾸십시오"원칙을 적용하십시오. 절대 회사에 feel 감을 느끼지 마십시오. 필요한 것보다 더 많은 것이 필요하므로 원하는 것을 제공하지 않으면 더 좋은 곳으로 걸어가십시오.
David Arno

4
나는 경력 동안 조금 움직였다. 코딩 방법에 대해 상사와 100 % 시선을 보인 적이 있다고 생각하지 않습니다. 우리는 자신의 배경과 철학을 가진 인간입니다. 그래서 개인적으로 내가 싫어하는 코딩 표준이 몇 개 있기 때문에 직장을 떠나지 않을 것입니다. (손가락을 굽히는 네이밍 컨벤션 관리자는 특히 내 장치에 남겨두면 코딩하는 방식과 상반되는 것으로 보입니다.) 그러나 괜찮은 프로그래머는 단순히 고용 상태를 유지하는 것에 대해 너무 걱정할 필요가 없습니다. .
user1172763

6

일반적으로 작은 기능을 사용하는 것이 좋습니다. 그러나 이상적으로 함수를 도입하면 큰 논리적 청크를 분리하거나 코드를 건조시켜 코드의 전체 크기를 줄여야한다고 생각합니다. 두 예제 모두 코드를 더 길게 만들고 개발자가 읽을 시간이 더 필요한 반면 짧은 대안은 "resourceId"가치가 프로덕션에만 존재 한다고 설명하지 않습니다 . 이와 같은 간단한 것은 특히 코드베이스를 처음 사용하는 경우 잊어 버리고 혼란스럽게 만듭니다.

나는 당신이 절대적으로 삼항을 사용해야한다고 말하지 않을 것입니다. 함께 일한 일부 사람들은 약간 더 길기를 선호합니다 if () {...} else {...}. 대부분 개인적인 선택입니다. 나는 "한 줄은 한 가지 접근 방식"을 선호하지만 기본적으로 코드베이스가 일반적으로 사용하는 것을 고수합니다.

3 진을 사용할 때 논리 검사로 행이 너무 길거나 복잡해지면 값을 보유 할 이름이 지정된 변수를 작성하는 것이 좋습니다.

// NOTE "resourceId" not present in dev build, use test data
let isProduction = 'resourceId' in headers;
let phoneNumber = isProduction ? headers.resourceId : DEV_PHONE_NUMBER;

또한 코드베이스가 300 줄 함수로 확장되면 일부 세분화가 필요하다고 말하고 싶습니다. 그러나 약간 더 넓은 스트로크를 사용하는 것이 좋습니다.


5

당신이 준 코드 예제는 상사입니다. 이 경우 하나의 명확한 선이 더 좋습니다.

일반적으로 복잡한 논리를 작은 조각으로 나누는 것이 가독성, 코드 유지 관리 및 서브 클래스의 동작이 약간 일 가능성에 더 좋습니다.

단점 : 함수 오버 헤드, 가려 짐 (함수는 함수가 주석과 함수 이름이 의미하는 것을 수행하지 않음), 복잡한 스파게티 논리, 죽은 기능의 가능성 (한 번에 더 이상 호출되지 않는 목적으로 만들어 짐)을 무시하지 마십시오 .


1
"함수 오버 헤드": 그것은 컴파일러에게 달려 있습니다. "모호함": OP가 해당 속성을 확인하는 유일한 방법인지 아니면 가장 좋은 방법인지를 나타내지 않았습니다. 당신은 확실히 알 수 없습니다. "복잡한 스파게티 논리": 어디? "죽은 기능의 가능성": 이런 종류의 불완전한 코드 분석은 성과가 낮고 결실이없는 개발 툴체인은 미성숙합니다.
Rhymoid

대답은 장점에 더 초점을 맞추었고 단점도 지적하고 싶었습니다. sum (a, b)와 같은 함수 호출은 항상 "a + b"보다 비쌉니다 (함수가 컴파일러에 의해 인라인되지 않는 한). 나머지 단점은 과도한 복잡성으로 인해 자체 문제가 발생할 수 있음을 보여줍니다. 나쁜 코드는 나쁜 코드이며, 작은 바이트로 나뉘어 있거나 300 라인 루프로 유지된다고해서 삼키기가 더 쉽다는 것을 의미하지는 않습니다.
Phil M

2

긴 함수를 선호하는 두 가지 이상의 주장을 생각할 수 있습니다.

  • 그것은 당신이 각 라인 주위에 많은 맥락을 가지고 있음을 의미합니다. 이를 공식화하는 방법 : 코드의 제어 흐름 그래프를 그립니다. 함수 입력과 함수 종료 사이의 정점 (~ = 라인)에서 모든 들어오는 모서리 를 알고 있습니다. 함수가 길수록 정점이 더 많아집니다.

  • 많은 작은 기능은 더 크고 복잡한 호출 그래프가 있음을 의미합니다. 임의 함수에서 임의의 행을 선택하고 "이 행이 어떤 컨텍스트에서 실행됩니까?"라는 질문에 대답하십시오. 이 그래프에서 더 많은 정점을 봐야하므로 콜 그래프가 더 크고 복잡해집니다.

긴 기능에 대한 논쟁도 있습니다 – 단위 테스트 가능성이 떠 오릅니다. 둘 중 하나를 선택할 때 t̶h̶e̶ ̶f̶o̶r̶c̶e̶ 경험을 사용하십시오.

참고 : 나는 당신의 상사가 옳다고 말하지 않고 단지 그의 관점이 완전히 가치가 없을 수도 있습니다.


내 생각에 좋은 최적화 매개 변수는 함수 길이가 아니라고 생각합니다. 나는 생각할 때 더 유용한 데 데라 타가 다음과 같다고 생각한다. 다른 모든 것이 동일하다면, 코드에서 비즈니스 로직과 구현에 대한 높은 수준의 설명을 읽을 수있는 것이 바람직하다. 관련 코드를 찾을 수 있으면 하위 수준 구현 세부 정보를 항상 읽을 수 있습니다.


David Arno의 답변에 대한 논평 :

작은 함수를 작성하면 코드가 무엇을하는지보기 위해 각 작은 함수로 이동해야하기 때문에 어려움이 있습니다.

함수의 이름이 잘 정해져 있다면 그렇지 않습니다. isApplicationInProduction은 자명하며 코드를 검사하여 코드의 기능을 확인할 필요는 없습니다. 실제로 반대의 경우도 마찬가지입니다. 코드를 검사하면 함수 이름보다 의도가 적습니다 (따라서 상사가 주석에 의존해야합니다).

이름은 반환 값의 의미를 분명하게 나타내지 만 코드 실행의 영향 에 대해서는 아무 것도 언급 하지 않습니다 (= 코드의 기능 ). 이름 (전용)은 의도 에 대한 정보를 전달하고 , 코드는 행동 에 대한 정보를 전달합니다 (의도의 일부가 추론 될 수 있음).

때때로 당신은 하나를 원하고 때로는 다른 것을 원하기 때문에이 관찰은 보편적으로 유효한 일방적 인 결정 규칙을 만들지 않습니다.

메인 루프가 300 줄 이상이더라도 모든 것을 메인 큰 루프에 넣으십시오. 읽기가 더 빠릅니다.

스캔하는 것이 더 빠를 수 있지만 실제로 코드를 "읽기"위해서는 머리에서 효과적으로 실행할 수 있어야합니다. 작은 기능으로는 쉽고 100 줄 길이의 방법으로는 실제로 어렵습니다.

나는 당신이 당신의 머리에서 그것을 실행해야한다는 것에 동의합니다. 하나의 큰 기능과 많은 작은 기능에서 500 줄의 기능이 있다면 왜 이것이 더 쉬운 지 분명하지 않습니다.

500 줄의 직선적으로 부작용이 많은 코드가 극단적 인 경우를 가정하고 효과 B가 효과 B 이전 또는 이후에 발생하는지 알고 자합니다. 큰 함수의 경우 Page Up / Down을 사용하여 두 행을 찾은 다음 비교하십시오. 줄 번호. 많은 작은 기능의 경우, 호출 트리에서 효과가 발생하는 위치를 기억해야하며 잊어 버린 경우이 트리의 구조를 재발견하는 데 사소한 시간을 소비해야합니다.

지원 기능의 콜 트리를 통과 할 때 비즈니스 로직에서 구현 세부 사항으로 전환 한 시점을 결정해야하는 어려움에 직면하게됩니다. 나는 콜 그래프가 단순할수록 이러한 구분을하는 것이 더 쉽다는 증거없이 주장한다 *.

(*) 적어도 나는 그것에 대해 정직합니다 ;-)

다시 한번, 나는 두 가지 접근법 모두 장단점이 있다고 생각합니다.

코드를 복제해야하는 경우 작은 함수 만 작성

동의하지 않습니다. 코드 예제에서 알 수 있듯이 이름이 작고 이름이 작은 함수는 코드의 가독성을 향상 시키며 예를 들어 "어떻게"에 관심이없고 기능의 "무엇"에만 관심이있을 때마다 사용해야합니다.

"방법"또는 "무엇"에 관심이 있는지 여부는 코드를 읽는 목적의 기능입니다 (예 : 일반적인 아이디어를 얻는 것과 버그를 추적하는 것). 프로그램을 작성하는 동안 코드를 읽는 목적을 사용할 수 없으며 다른 목적으로 코드를 읽을 가능성이 높습니다. 다른 결정은 다른 목적에 맞게 최적화됩니다.

즉, 이것은 아마도 내가 가장 동의하지 않는 보스의 견해의 일부입니다.

주석의 이름으로 함수를 작성하지 말고 위의 주석과 함께 복잡한 코드 줄 (3-4 행)을 넣으십시오. 이와 같이 실패한 코드를 직접 수정할 수 있습니다

나는 이것이 진지하다고 가정 할 때, 이것에 대한 추론을 정말로 이해할 수 없다. [...] 주석은 근본적인 결함이 있습니다. 그것들은 컴파일 / 해석되지 않았으므로 단위 테스트를 할 수 없습니다. 코드가 수정되고 주석이 홀로 남게되어 어느 쪽이 옳은지 알 수 없게됩니다.

컴파일러는 이름이 동일한 지 비교 만하며 MisleadingNameError를 제공하지 않습니다. 또한 여러 호출 사이트가 이름으로 지정된 기능을 호출 할 수 있기 때문에 이름을 변경하는 것이 더 힘들고 오류가 발생하기 쉽습니다. 주석에는이 문제가 없습니다. 그러나 이것은 다소 투기 적입니다. 실제로 이것을 해결하려면 프로그래머가 오해의 소지가있는 의견과 오해의 소지가있는 이름을 업데이트 할 가능성이 더 높은지에 대한 데이터가 필요할 것입니다.


-1

제 생각에는 필요한 기능에 대한 올바른 코드는 다음과 같습니다.

phoneNumber = headers.resourceId || DEV_PHONE_NUMBER;

또는 함수로 나누려면 다음과 같이하십시오.

phoneNumber = getPhoneNumber(headers);

function getPhoneNumber(headers) {
  return headers.resourceId || DEV_PHONE_NUMBER
}

하지만 "생산 중"이라는 개념에 더 근본적인 문제가 있다고 생각합니다. 함수의 문제점 isApplicationInProduction은 이것이 "제작"에있는 시스템에서 유일한 장소이며, 항상 resourceId 헤더의 존재 또는 부재에 의존하여 알려줄 수 있다는 것이 이상하게 보인다는 것입니다. 환경을 직접 점검 하는 일반적인 isApplicationInProduction방법 이 있어야합니다 getEnvironment. 코드는 다음과 같아야합니다.

function isApplicationInProduction() {
  process.env.NODE_ENV === 'production';
}

그런 다음 전화 번호를 얻을 수 있습니다 :

phoneNumber = isApplicationInProduction() ? headers.resourceId : DEV_PHONE_NUMBER;

-2

총알 두 개에 대한 의견

  • 작은 함수를 작성하면 코드가 무엇을하는지보기 위해 각각의 작은 함수로 이동해야하기 때문에 고통 스럽다.
  • 메인 루프가 300 줄 이상이더라도 모든 것을 메인 큰 루프에 넣으십시오. 읽기가 더 빠릅니다.

많은 편집기 (예 : IntelliJ)를 사용하면 Ctrl- 클릭하여 사용법을 함수 / 클래스로 바로 이동할 수 있습니다. 또한 코드를 읽기 위해 함수의 구현 세부 사항을 알 필요가 없으므로 코드 읽기 속도가 더 빠릅니다.

나는 당신이 당신의 상사에게 말할 것을 권장합니다; 그는 당신의 옹호를 좋아하고 그것을 리더십으로 볼 것입니다. 예의 바르게 행동하십시오.

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