Clean Code의 블록과 추가 매개 변수에 추가 라인


33

문맥

에서 클린 코드 , 35 페이지, 그것은 말한다

이는 if 문, else 문, while 문 등의 블록은 한 줄 길이 여야 함을 의미합니다. 아마도 그 라인은 함수 호출이어야합니다. 이렇게하면 둘러싼 함수가 작게 유지 될뿐만 아니라 블록 내에서 호출 된 함수의 이름이 잘 설명 될 수 있으므로 다큐멘터리 값도 추가됩니다.

나는 완전히 동의합니다.

나중에 40 페이지의 함수 인수에 대해 설명합니다.

함수에 대한 이상적인 인수 수는 0입니다 (나일론). 다음은 하나 (모나 딕)가오고 그 뒤에는 2 (이차)가옵니다. 가능한 경우 세 가지 주장 (삼국 법)을 피해야합니다. 3 개 이상 (다 극적)은 매우 특별한 타당성을 요구하므로 어쨌든 사용해서는 안됩니다. 인수는 어렵다. 그들은 많은 개념적 힘을 취합니다.

나는 완전히 동의합니다.

발행물

그러나 나는 종종 다른 목록에서 목록을 작성하는 것을 발견하고 두 가지 악 중 하나와 함께 살아야합니다.

어느 내가 블록에 두 줄을 사용하여 , 결과에 추가하는 것은, 하나를 만들기위한 하나

    public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
    {
        List<Flurp> flurps = new List<Flurp>();
        foreach (BadaBoom badaBoom in badaBooms)
        {
            Flurp flurp = CreateFlurp(badaBoom);
            flurps.Add(flurp);
        }
        return flurps;
    }

또는 항목을 추가 할 목록 의 함수인수를 추가하여 "한 인수를 더 악화시킵니다".

    public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
    {
        List<Flurp> flurps = new List<Flurp>();
        foreach (BadaBoom badaBoom in badaBooms)
        {
            CreateFlurpInList(badaBoom, flurps);
        }
        return flurps;
    }

의문

내가 보지 못하는 (단점) 장점이 있습니까? 또는 특정 상황에서 그러한 이점이 있습니까? 이 경우 결정을 내릴 때 무엇을 찾아야합니까?


58
무슨 일이야 flurps.Add(CreateFlurp(badaBoom));?
cmaster

47
아니, 그것은 단지 하나의 진술입니다. 단순하게 중첩 된 표현 일뿐입니다 (단일 중첩 수준). 그리고 간단한 f(g(x))스타일 가이드에 맞지 않으면 스타일 가이드를 고칠 수 없습니다. 내 말은, sqrt(x*x + y*y)네 줄로 나뉘 지 않습니까? 그리고 그것은 두 개의 내부 중첩 수준 (gasp!)에 대한 세 개의 중첩 된 하위 표현식입니다! 목표는 단일 연산자 설명이 아닌 가독성 이어야합니다 . 나중에 원한다면, 당신을위한 완벽한 언어가 있습니다 : 어셈블러.
cmaster

6
@cmaster x86 어셈블리조차 단일 연산자 문을 엄격하게 가지고 있지 않습니다. 메모리 주소 지정 모드에는 많은 복잡한 작업이 포함되며 산술에 사용할 수 있습니다. 실제로 x86 mov명령어 만 사용 jmp toStart하고 끝에는 하나만 사용하여 Turing-complete 컴퓨터를 만들 수 있습니다 . 누군가 실제로 그것을 정확하게 수행하는 컴파일러를 만들었습니다 : D
Luaan

5
@Luaan rlwimiPPC에 대한 악명 높은 지시는 말할 것도 없습니다 . (이것은 왼쪽 단어 즉시 마스크 삽입 회전을 나타냅니다.)이 명령은 5 개의 피연산자 (2 개의 레지스터 및 3 개의 즉각적인 값)를 가져야하며 다음과 같은 작업을 수행했습니다. 즉, 2 개의 다른 즉시 피연산자에 의해 제어되는 1 비트의 단일 런으로 생성되고 다른 레지스터 피연산자의 마스크에서 1 비트에 해당하는 비트는 회전 된 레지스터의 해당 비트로 대체되었습니다. 매우 멋진 수업 :-)
cmaster

7
@ R.Schmitz "범용 프로그래밍을하고있다"-실제로, 당신은 특정 목적을 위해 프로그래밍을하고 있지 않다 ( 어떤 목적을 모르지만 당신 은 ;-)을 가정하고있다 . 말 그대로 프로그래밍에는 수천 가지 목적이 있으며, 그에 대한 최적의 코딩 스타일은 다양합니다. 따라서 여러분에게 적합한 것은 다른 사람에게는 적합하지 않을 수 있으며, 그 반대도 마찬가지입니다. 종종 조언은 절대적입니다 ( " 항상 X를 수행하십시오; Y는 나쁘다" "등)을 무시하면 일부 영역에서는 완전히 비현실적입니다. 그래서 Clean Code와 같은 책의 조언은 항상 (실제적인) 소금 한
덩어리로 찍어야합니다.

답변:


104

이 지침은지도가 아닌 나침반입니다. 그들은 합리적인 방향으로 당신을 가리 킵니다 . 그러나 어떤 솔루션이“최고의”솔루션인지 절대적으로 말할 수는 없습니다. 어느 시점에서 목적지에 도착했기 때문에 나침반이 가리키는 방향으로 걷기를 중단해야합니다.

Clean Code는 코드를 매우 작고 명백한 블록으로 나누도록 권장합니다. 그것은 일반적으로 좋은 방향입니다. 그러나 인용 된 조언을 문자 그대로 해석하여 극단적으로 가져 가면 코드를 쓸모없는 작은 조각으로 세분화합니다. 실제로는 아무것도하지 않으며 모든 것이 위임됩니다. 이것은 본질적으로 또 다른 종류의 코드 난독 화입니다.

“너무 작을수록 쓸모가 없다”와“작을수록 좋다”의 균형을 맞추는 것이 당신의 일입니다. 어떤 솔루션이 더 간단한 지 스스로에게 물어보십시오. 필자에게는 분명히 목록을 구성하는 첫 번째 솔루션 입니다. 이것은 잘 이해되는 관용구입니다. 또 다른 기능을 보지 않고도 해당 코드를 이해할 수 있습니다.

더 잘 할 수 있다면,“목록에서 다른 목록으로 모든 요소 변환”은 기능적 map()연산 을 사용하여 추상화 될 수있는 일반적인 패턴이라는 점에 주목해야 합니다. C #에서는이라고 Select합니다. 이 같은:

public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
    return badaBooms.Select(BadaBoom => CreateFlurp(badaBoom)).ToList();
}

7
코드는 여전히 잘못되어 바퀴를 무의식적으로 재발 명합니다. CreateFlurps(someList)BCL이 이미 제공 할 때 전화 해야하는 이유 는 someList.ConvertAll(CreateFlurp)무엇입니까?
벤 Voigt

44
@BenVoigt 이것은 디자인 수준의 질문입니다. 특히 화이트 보드에 컴파일러가 없기 때문에 정확한 구문에 관심 이 없습니다 . ( '09에서 C #을 마지막으로 작성했습니다). 내 요점은“최고의 가능한 코드를 보여주었습니다”가 아니라“이것은 이미 해결 된 일반적인 패턴입니다. LINQ는 그것을 할 수있는 한 가지 방법, 당신이 언급 ConvertAll입니다 다른 . 대안을 제안 해 주셔서 감사합니다.
amon

1
귀하의 답변은 합리적이지만 LINQ는 논리를 추상화하고 진술을 한 줄로 줄인다는 사실이 귀하의 조언과 모순되는 것처럼 보입니다. 부수적으로, BadaBoom => CreateFlurp(badaBoom)여분이다; CreateFlurp함수로 직접 전달할 수 있습니다 ( Select(CreateFlurp)). (내가 아는 한, 이것은 항상
그렇습니다

2
이렇게하면 메소드가 필요하지 않습니다. 이름 CreateFlurps은 실제로 보는 것보다 더 오해의 소지가 있고 이해하기가 어렵습니다 badaBooms.Select(CreateFlurp). 후자는 완전히 선언적입니다. 분해하는 데 아무런 문제가 없으므로 방법이 전혀 필요하지 않습니다.
Carl Leth

1
R.Schmitz @이 아니라 하드 이해하지만, 그건 쉽지 보다 이해하기 badaBooms.Select(CreateFlurp). 이름 (상위 레벨)이 구현 (하위 레벨)을 나타내도록 메소드를 작성합니다. 이 경우 그들은 같은 수준에 있으므로 정확히 무슨 일이 일어나고 있는지 알아 보려면 인라인으로 보는 대신 방법을 살펴 봐야합니다. CreateFlurps(badaBooms)놀람을 가질 수는 있지만 badaBooms.Select(CreateFlurp)할 수는 없습니다. 또한 List대신을 잘못 요청하여 오도의 소지 가 IEnumerable있습니다.
Carl Leth

61

함수에 대한 이상적인 인수 수는 0입니다 (나일론)

아니! 함수에 대한 이상적인 인수 수는 하나입니다. 값이 0이면 작업을 수행 할 수 있도록 함수가 외부 정보에 액세스해야합니다. "삼촌"밥이 이걸 매우 잘못했습니다.

코드와 관련하여 첫 번째 예제는 첫 번째 행에 로컬 변수를 작성하기 때문에 블록에 두 개의 행만 있습니다. 해당 할당을 제거하면 다음과 같은 깨끗한 코드 지침을 준수하게됩니다.

public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
    List<Flurp> flurps = new List<Flurp>();
    foreach (BadaBoom badaBoom in badaBooms)
    {
        flurps.Add(CreateFlurp(badaBoom));
    }
    return flurps;
}

그러나 그것은 매우 긴 감기 (C #) 코드입니다. 그냥 다음과 같이하십시오 :

IEnumerable<Flurp> CreateFlurps(IEnumerable<BadaBoom> badaBooms) =>
    from badaBoom in babaBooms select CreateFlurp(badaBoom);

14
인수가 0 인 함수는 객체가 객체 외부의 전역 상태에 존재하지 않고 필요한 데이터를 캡슐화 함을 의미합니다.
Ryathal

19
@Ryathal, 두 가지 점 : (1) 메소드를 말하고있는 경우 대부분의 (OO?) OO 언어의 경우 해당 오브젝트가 첫 번째 매개 변수로 유추됩니다 (또는 Python의 경우 명시 적으로 언급 됨). Java, C # 등에서 모든 메소드는 하나 이상의 매개 변수가있는 함수입니다. 컴파일러는 그 세부 정보를 숨 깁니다. (2) 나는 "글로벌"에 대해 언급 한 적이 없다. 객체 상태는 예를 들어 메소드 외부에 있습니다.
David Arno

17
밥 삼촌이 "제로"를 썼을 때 그는 "제로 (이것을 세지 않음)"를 의미한다고 확신합니다.
Doc Brown

26
@DocBrown은 아마도 객체에서 상태와 기능을 혼합하는 데 큰 관심을 가지고 있기 때문에 아마도 "함수"에 의해 구체적으로 방법을 언급 할 것입니다. 그리고 나는 여전히 그에게 동의하지 않습니다. 원하는 것을 얻기 위해 객체를 망쳐 놓는 것보다는 필요한 것만 메소드에 제공하는 것이 훨씬 낫습니다 (즉, 작동중인 고전적인 "말하고 묻지 말 것").
David Arno

8
@AlessandroTeruzzi, 이상적인 것은 하나의 매개 변수입니다. 제로는 너무 적습니다. 예를 들어, 기능 언어는 카레 목적으로 하나의 매개 변수 수로 하나를 채택하는 이유입니다 (사실 일부 기능 언어에서는 모든 기능에 정확히 하나의 매개 변수가 있습니다. 매개 변수가 0 인 카레는 의미가 없습니다. "이상적인 것이 가능한 한 적고, 에르고 제로가 최고입니다"라고 말하는 것이 reductio ad absurdum 의 예입니다 .
David Arno

19

'클린 코드'조언은 완전히 잘못되었습니다.

루프에 두 개 이상의 줄을 사용하십시오. 함수에서 동일한 두 줄을 숨기는 것은 설명이 필요한 임의의 수학 일 때 의미가 있지만, 줄이 이미 기술적 인 경우에는 아무 것도하지 않습니다. '만들기'와 '추가'

두 번째 줄을 피하기 위해 두 번째 인수를 추가하지 않아도되므로 두 번째 방법은 실제로 의미가 없습니다.

public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
    {
        List<Flurp> flurps = new List<Flurp>();
        foreach (BadaBoom badaBoom in badaBooms)
        {
            flurps.Add(badaBoom .CreateFlurp());
            //or
            badaBoom.AddToListAsFlurp(flurps);
            //or
            flurps.Add(new Flurp(badaBoom));
            //or
            //make flurps a member of the class
            //use linq.Select()
            //etc
        }
        return flurps;
    }

또는

foreach(var flurp in ConvertToFlurps(badaBooms))...

다른 사람들이 지적했듯이, 최상의 기능은 인수가없는 기능이라는 조언은 OOP로 기울어지고 최악의 경우에는 명백한 나쁜 조언입니다


더 명확하게하기 위해이 답변을 편집하고 싶습니까? 내 질문은 Clean Code에서 한 가지가 다른 것보다 중요하다면. 당신은 모든 것이 잘못되었다고 말한 다음 계속해서 내가 준 옵션 중 하나를 설명합니다. 현재이 답변은 실제로 질문에 대한 답변을 시도하는 대신 클린턴 코드 안건을 따르는 것 같습니다.
R. Schmitz

죄송합니다. 귀하의 질문을 첫 번째는 '정상적인'방법이라고 제안하는 것으로 해석되었지만 두 번째로 밀려났습니다. 나는 일반적으로 안티 클린 코드는 아니지만이 인용은 분명히 잘못되었습니다.
Ewan

19
@ R.Schmitz 저는 "Clean Code"를 직접 읽었으며 그 책의 대부분을 따릅니다. 그러나 완벽한 함수 크기는 거의 하나의 문장이므로, 그것은 명백한 잘못입니다. 유일한 효과는 스파게티 코드를 쌀 코드로 변환하는 것입니다. 독자는 함께 볼 때 합리적인 의미를 만들어내는 수많은 사소한 기능에서 길을 잃습니다. 인간은 작업 메모리 용량이 제한되어 있으며 명령문이나 함수로 과부하 할 수 있습니다. 읽을 수있게하려면 둘 사이의 균형을 유지해야합니다. 극단을 피하십시오!
cmaster

@ cmaster 대답은 내가 그 의견을 쓸 때 처음 두 단락에 불과했습니다. 지금은 더 나은 방법입니다.
R. Schmitz

7
솔직히 나는 짧은 대답을 선호했다. 이 답변의 대부분에 외교적 대화가 너무 많습니다. 인용 된 조언은 명백한 잘못이며, '실제로 의미하는 바'에 대해 추측하거나 좋은 해석을 찾기 위해 뒤 틀릴 필요가 없습니다.
Ewan

15

두 번째는 CreateFlurpInList목록 을 수락하고 해당 목록을 수정하여 함수가 순수하지 않고 추론하기가 어렵 기 때문에 확실히 더 나쁩니다 . 메소드 이름의 어떤 것도 메소드가 목록에만 추가한다고 제안하지 않습니다.

그리고 나는 세 번째 최선의 옵션을 제공합니다.

public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
    return badaBooms.Select(CreateFlurp).ToList();
}

그리고 하나의 라이너 자체가 명확하기 때문에 사용되는 장소가 하나 뿐인 경우 즉시 해당 메소드를 인라인 할 수 있으므로 의미를 부여하기 위해 메소드로 캡슐화 할 필요가 없습니다.


나는 그 방법에 대해 "순수하고 추론하기가 어렵지 않다"(그렇지만 사실)에 대해 너무 불평하지는 않지만, 특별한 경우를 처리하기 위해 완전히 불필요한 방법이라는 것에 대해 불평하지 않을 것입니다. 독립형 Flurp, 배열에 추가 된 Flurp, 사전, Flurp를 사전에서 조회하고 일치하는 Flurp 등을 제거하려면 어떻게해야합니까? 같은 주장으로 Flurp 코드에는 이러한 모든 방법이 필요합니다.
gnasher729

10

하나의 인수 버전이 더 좋지만, 주로 인수의 수로 인한 것은 아닙니다.

가장 중요한 이유는 커플 링낮아서 더 유용하고, 추론하기 쉽고, 테스트하기 쉽고, 복사 + 붙여 넣기 된 복제본으로 전환 할 가능성이 적기 때문입니다.

당신이 저를 제공하는 경우 CreateFlurp(BadaBoom)간단한 : 나는 수집 컨테이너의 모든 유형이 사용할 수 있습니다 Flurp[], List<Flurp>, LinkedList<Flurp>, Dictionary<Key, Flurp>, 등. 하지만와 함께 내 뷰 모델에서 목록이 변경되었다는 알림을받을 수 있도록 CreateFlurpInList(BadaBoom, List<Flurp>)내일 다시 방문 할 예정 CreateFlurpInBindingList(BadaBoom, BindingList<Flurp>)입니다. 왝!

추가적인 이점으로 서명이 단순할수록 기존 API에 더 적합 할 수 있습니다. 반복되는 문제가 있다고

오히려 다른 목록에서 목록을 작성하는 경우가 종종 있습니다.

사용 가능한 도구를 사용하기 만하면됩니다. 가장 짧고 효율적이며 최상의 버전은 다음과 같습니다.

var Flurps = badaBooms.ConvertAll(CreateFlurp);

이 코드는 작성 및 테스트를위한 코드가 적을뿐만 아니라 List<T>.ConvertAll()결과가 입력과 동일한 수의 항목을 갖고 결과 목록을 올바른 크기로 미리 할당 할 수있을 정도로 똑똑하기 때문에 더 빠릅니다 . 코드 (두 버전 모두)에서 목록을 확장해야했습니다.


를 사용하지 마십시오 List.ConvertAll. 열거 가능한 개체를 C #의 다른 개체에 매핑하는 관용적 방법을이라고 Select합니다. 여기에서 유일한 이유 ConvertAll는 OP가 List메소드에서 잘못을 요청하고 있기 때문 IEnumerable입니다.
Carl Leth

6

코드를 쉽게 읽고 유지 관리 할 수 ​​있도록 전반적인 목표를 명심하십시오.

종종 여러 줄을 하나의 의미있는 기능으로 그룹화 할 수 있습니다. 이 경우 그렇게하십시오. 때때로, 당신은 일반적인 접근법을 재고해야 할 것입니다.

예를 들어, 전체 구현을 var로 바꾸는 경우

flups = badaBooms.Select(bb => new Flurp(bb));

가능성이 있습니다. 아니면 당신은 같은 것을 할 수 있습니다

flups.Add(new Flurp(badaBoom))

때로는 가장 깨끗하고 읽기 쉬운 솔루션이 한 줄에 맞지 않을 수도 있습니다. 두 줄이 있습니다. 임의의 규칙을 이행하기 위해 코드를 이해하기 어렵게 만들지 마십시오.

두 번째 예는 (제 생각에) 첫 번째 것보다 이해하기가 상당히 어렵습니다. 두 번째 매개 변수가 아니라 매개 변수가 함수에 의해 수정 된 것입니다. Clean Code가 무엇에 대해 말하고 있는지 찾아보십시오. (현재 책을 가지고 있지는 않지만 기본적으로 "피할 수 있다면 그렇게하지 마십시오"라고 확신합니다).

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