항상 두 번 반복해야하는 기능을 작성하는 것이 가장 좋은 방법입니까?


131

나 자신, 나는 두 번 이상 무언가를해야 할 때 함수를 작성하기를 기다릴 수 없다. 그러나 두 번만 나타나는 것에 관해서는 조금 더 까다 롭습니다.

두 줄 이상이 필요한 코드의 경우 함수를 작성하겠습니다. 그러나 다음과 같은 일에 직면 할 때 :

print "Hi, Tom"
print "Hi, Mary"

나는 쓰는 것을 주저합니다 :

def greeting(name):
    print "Hi, " + name

greeting('Tom')
greeting('Mary')

두 번째는 너무 많이 보이지 않습니까?


그러나 우리가 가지고 있다면 :

for name in vip_list:
    print name
for name in guest_list:
    print name

그리고 여기 대안이 있습니다 :

def print_name(name_list):
    for name in name_list:
        print name

print_name(vip_list)
print_name(guest_list)

상황이 까다로워 지나요? 지금 결정하기가 어렵습니다.

이것에 대한 당신의 의견은 무엇입니까?


145
나는 치료 alwaysnever붉은 깃발있다. 은 "컨텍스트"라는 것은,있다 alwaysnever도 일반적으로 좋은 경우 규칙은, 적절한 것을하지 않을 수는. 절대적으로 다루는 소프트웨어 개발자를 조심하십시오. ;)
async

7
마지막 예에서 다음을 수행 할 수 있습니다 from itertools import chain; for name in chain(vip_list, guest_list): print(name)..
Bakuriu

14
@ user16547 우리는 이것을 'Sithware 개발자'라고 부릅니다!
Brian

6
Tim Peters의 Python Zen에서 :Special cases aren't special enough to break the rules. Although practicality beats purity.
Zenon

3
"damp code"또는 "dry code"를 원하는 경우에도 고려하십시오. stackoverflow.com/questions/6453235/…
Ian

답변:


201

함수를 분리하기로 결정하는 것은 한 가지 요소이지만, 반복되는 횟수가 유일한 요소는 아닙니다. 한 번만 실행되는 무언가를위한 함수를 만드는 것이 종종 합리적 입니다. 일반적으로 다음과 같은 경우 기능을 분할하려고합니다.

  • 각 개별 추상화 계층을 단순화합니다.
  • split-off 함수의 이름은 훌륭하고 의미가 있으므로 일반적으로 추상화 계층 사이를 이동하여 상황을 이해하지 않아도됩니다.

귀하의 예가 해당 기준을 충족하지 않습니다. 당신은 하나의 라이너에서 하나의 라이너로 가고 있으며, 이름은 명확성 측면에서 아무것도 사지 않습니다. 즉, 간단한 기능은 튜토리얼 및 학교 과제 이외에는 드 rare니다. 대부분의 프로그래머는 다른 방식으로 너무 잘못하는 경향이 있습니다.


이것이 RobertHarvey의 대답에서 놓친 요점입니다.
Doc Brown

1
과연. 복잡한 함수를 인라인 서브 루틴으로 나눕니다. 익명의 범위도 좋습니다.
imallett

3
범위 문제도 있습니다. 그것은 아마 쓸 이해가되지 않습니다 print_name (NAME_LIST) 전체 클래스 또는 전체 모듈이 볼 수있는 기능을하지만, 수도 (이 경우 여전히 스트레칭)을 정리하기 위해 함수 내에서 로컬 함수를 만들기 위해 이해 몇 줄의 반복되는 코드.
Joshua Taylor

6
추가해야 할 또 다른 요점은 표준화입니다. 첫 번째 예에서는 인사를하고 있으며, 두 사람을 동일하게 인사 할 수 있도록이 기능을 기능에 적용하는 것이 합리적 일 수 있습니다. 특히 인사말이 미래에 변경 될 수 있음을 고려하면 :)
Svish

1
+1 단지 한 번만 실행되는 무언가를위한 함수를 만드는 것이 종종 합리적입니다. 로켓 엔진의 동작을 모델링 한 함수에 대한 테스트를 설계하는 방법을 한 번 물었습니다. 이 함수의 순환 복잡성은 90 년대에 있었고 90 가지 경우가있는 스위치 문이 아니 었습니다 (추악하지만 테스트 가능). 대신 엔지니어가 작성한 복잡한 혼란이었고 완전히 테스트 할 수 없었습니다. 내 대답은 단지 테스트 할 수 없으며 다시 작성해야한다는 것입니다. 그들은 내 충고를 따랐다!
David Hammen

104

중복이 실수 가 아닌 의도적 인 경우에만 가능합니다 .

또는 다른 방법으로 넣으십시오.

미래에 그들이 함께 진화 할 것으로 예상되는 경우에만 .


이유는 다음과 같습니다.

때로는 두 개의 코드 가 서로 관련이 없지만 동일하게 발생 합니다. 이 경우 다음 번에 누군가 유지 보수를 수행 할 때 변경 사항이 이전에 존재하지 않았던 발신자에게 전파 될 것으로 예상 하지 않으므로 해당 기능이 중단 될 수 있으므로 이를 결합하려는 충동에 저항 해야합니다 . 따라서 코드 크기를 줄이는 것처럼 보일 때가 아니라 코드가 의미가있을 때만 코드를 제외해야합니다.

경험 법칙 :

코드가 새 데이터 만 반환하고 기존 데이터를 수정하지 않거나 다른 부작용이없는 경우 별도의 함수로 제외하는 것이 안전 할 가능성이 큽니다. (함수의 의미를 완전히 변경하지 않고 파손을 일으킬 수있는 시나리오는 상상할 수 없습니다.이 시점에서 함수 이름이나 서명도 변경되어야하므로 어쨌든주의해야합니다. )


12
+1 이것이 가장 중요한 고려 사항이라고 생각합니다. 코드 변경을 마지막으로 예상하는 코드를 리팩터링하는 사람은 없으므로 변경 사항이 향후 코드 변경에 어떤 영향을 미치는지 고려하십시오.
yoniLavi

2
루비이지만 Avdi Grimm의 우연의 중복에 관한이 스크린 캐스트는 여전히 youtube.com/watch?v=E4FluFOC1zM
DGM

60

아니요, 항상 모범 사례는 아닙니다.

다른 모든 것들은 동일하고 선형이며 라인 단위 코드이며 함수 호출을 뛰어 넘는 것보다 읽기 쉽습니다. 사소한 함수 호출은 항상 매개 변수를 취하므로 모든 것을 정렬하고 함수 호출에서 함수 호출로 정신 컨텍스트 점프를해야합니다. 모호한 이유가없는 한 (예 : 필요한 성능 향상을 얻는 등) 코드 선명도를 높이십시오.

그렇다면 왜 코드가 별도의 메소드로 리팩토링됩니까? 모듈성을 향상시킵니다. 개별 메소드 뒤에 중요한 기능 을 수집 하고 의미있는 이름을 지정합니다. 그것을 달성하지 못하면 별도의 방법이 필요하지 않습니다.


58
코드 가독성이 가장 중요합니다.
톰 로빈슨

27
함수를 만드는 가장 큰 이유 중 하나를 잊어 버린 적이 없다면이 대답을 피 투명 했을 것입니다. 기능에 좋은 이름을 부여 하여 추상화만드는 것입니다. 그 점을 추가했다면, 선형의 라인 별 코드가 올바른 추상화를 사용하는 코드보다 항상 읽기 쉽지 는 않다는 것이 분명합니다 .
Doc Brown

17
Doc Brown에 동의합니다. 코드가 올바르게 추상화 될 때보 다 한 줄씩 더 읽기 쉽습니다. 좋은 함수 이름을 사용하면 구현이 아닌 의도를 읽고 있기 때문에 높은 수준의 함수를 매우 쉽게 읽을 수 있으며, 하나의 작업, 정확히 하나의 작업을 수행하고 해당 작업 만 수행하기 때문에 낮은 수준의 기능을 쉽게 읽을 수 있습니다. 이것은 기능을 간결하고 정확하게 만듭니다.
Jon Story

19
Why take the performance hit of a function call when you don't get any significant benefit by doing so?어떤 성능이 저하됩니까? 일반적으로 작은 함수는 인라인되고 큰 함수에는 오버 헤드가 무시됩니다. CPython은 아마도 인라인하지 않지만 성능을 위해 아무도 사용하지 않습니다. 나는 또한 line by line code...조금 질문한다 . 당신의 두뇌에서 실행을 시뮬레이션하려고하면 더 쉽습니다. 그러나 디버거는 더 나은 작업을 수행하고 루프를 실행하여 루프에 대해 무언가를 증명하는 것은 정수를 반복하여 정수에 대한 명령문을 증명하는 것과 같습니다.
Doval

6
@Doval은 매우 유효한 포인트를 발생시킵니다. 코드가 컴파일되면 함수가 인라인 (및 복제)되므로 런타임 성능에 영향을 미치지 않습니다 (컴파일에 작은 기능이 있음). 해석 된 언어에는 해당되지 않지만 함수 호출은 실행 시간의 1 분의 1 비율입니다.
Jon Story

25

특정 예제에서 함수를 만드는 것은 지나친 것처럼 보일 수 있습니다. 대신 나는 질문을 할 것이다 :이 특별한 인사말이 미래에 변할 가능성이 있는가? 어떻게 요?

기능은 단순히 기능을 마무리하고 재사용을 용이하게하는 데 사용될뿐만 아니라 수정을 용이하게하기 위해 사용됩니다. 요구 사항이 변경되면 복사 붙여 넣은 코드를 찾아서 수동으로 변경해야하는 반면, 기능을 사용하면 코드를 한 번만 수정하면됩니다.

논리는 거의 존재하지 않기 때문에 함수를 통해이 혜택을 얻을 수 있지만 GREET_OPENING상수는 가능합니다 . 그런 다음이 상수를 파일에서로드하여 프로그램을 다른 언어에 쉽게 적용 할 수 있습니다. 이것은 조잡한 해결책이며, 더 복잡한 프로그램은 아마도 i18n을 추적하기 위해 더 세련된 방법이 필요할 것입니다.

그것은 결국 가능한 요구 사항에 관한 것이며 미래의 자기 일을 쉽게하기 위해 미리 계획을 세우는 것입니다.


GREET_OPENING 상수를 사용하면 가능한 모든 인사말의 순서가 동일하다고 가정합니다. 모국어가 영어와 거의 반대되는 일을하는 사람과 결혼하면이 가정을 무시하게됩니다.
Loren Pechtel

22

개인적으로 저는 YAGNI 에게 전화하여 정당화 할 세 가지 규칙을 채택했습니다 . 일단 코드를 작성하고 나면 무언가를해야한다면, 두 번만 복사 / 붙여 넣기 (예, 그냥 복사 / 붙여 넣기를 인정한 것입니다!)하지만 다시는 필요하지 않을 것입니다. 다시 한 번 리팩토링하고 해당 청크를 자체 방법으로 추출 합니다. 다시 한 번 설명 하겠습니다 .

나는 칼 빌레펠트와 로버트 하비가 말한 것에 동의하며 그들이하는 말에 대한 나의 해석은 가장 중요한 규칙이 가독성이라는 것입니다. 내 코드를 더 쉽게 읽을 수있게하면 함수 작성을 고려하십시오 .DRYSLAP 과 같은 것을 명심하십시오 . 함수에서 추상화 수준을 변경하지 않아도 머리에서 관리하기가 더 쉽다는 것을 알았습니다. 따라서 함수 간을 뛰어 넘지 마십시오 (함수 이름을 읽음으로써 단순히 기능이 무엇인지 이해하지 못하는 경우) 정신 과정.
마찬가지로 함수와 인라인 코드 사이에서 컨텍스트를 전환 할 필요가 없습니다 print "Hi, Tom".이 경우 함수 iff를 추출 할 수 있습니다PrintNames() 전체적인 나머지 함수는 대부분 함수 호출이었습니다.


3
c2 위키는 3의 규칙에 관한 좋은 페이지를 가지고 있습니다 : c2.com/cgi/wiki?RuleOfThree
pimlottc

13

드물게 잘리지 않으므로 옵션을 계량해야합니다.

  • 마감일 (수정 버닝 서버 룸 최대한 빨리)
  • 코드 가독성 (선택에 영향을 줄 수 있음)
  • 공유 로직의 추상화 수준 (위와 관련됨)
  • 재사용 요구 사항 (즉, 정확히 동일한 논리가 중요하거나 지금 당장 편리함)
  • 공유의 어려움 (파이썬이 빛나는 곳, 아래 참조)

C ++에서는 보통 3의 규칙을 따릅니다 (즉, 세 번째로 같은 것을 필요로 할 때 올바르게 공유 할 수있는 부분으로 리팩터링합니다). 경험이 있으면 범위에 대해 더 많이 알면 처음에 선택을하는 것이 더 쉽습니다. 작업중인 소프트웨어 및 도메인

그러나 파이썬에서는 논리를 반복하기보다는 재사용하는 것이 상당히 가볍습니다. 다른 많은 언어들 (적어도 역사적으로는)보다 더 많은 IMO.

따라서 로컬 인수에서 목록을 작성하여 논리를 로컬에서 다시 사용하는 것이 좋습니다.

def foo():
    for name_list in (vip_list, guest_list): # can be list of tuples, for many args
        for name in name_list:
            print name

여러 인수가 필요한 경우 튜플 튜플을 사용하여 for 루프로 직접 분할 할 수 있습니다.

def foo2():
    for header, name_list in (('vips': vip_list), ('people': guest_list)): 
        print header + ": "
        for name in name_list:
            print name

또는 로컬 함수 (두 번째 예일 수도 있음)를 작성하면 논리가 명시 적으로 재사용되지만 print_name ()이 함수 외부에서 사용되지 않음을 분명히 알 수 있습니다.

def foo():
    def print_name(name_list):
        for name in name_list:
            print name

    print_name(vip_list)
    print_name(guest_list)

특히 중간에 로직을 중단해야하는 경우 (예 : 리턴 사용) 함수가 바람직합니다.

동일한 로직, IMO를 반복하는 것보다 우수하며 한 명의 호출자 만 사용하는 전역 / 클래스 함수를 선언하는 것 (두 번이더라도)보다 덜 복잡합니다.


이 답변을보기 전에 새로운 기능 의 범위 에 대해 비슷한 의견을 게시 했습니다 . 질문의 해당 측면을 다루는 답변이있어서 다행입니다.
Joshua Taylor

1
+1-이것은 OP가 찾고있는 일종의 대답은 아니지만 적어도 그가 질문 한 질문에 대해서는 이것이 가장 현명한 반응이라고 생각합니다. 이것은 아마도 당신을 위해 언어로 구워 질 것입니다.
Patrick Collins

@ 패트릭 콜린스 : 투표 주셔서 감사합니다! :) 답변을보다 완벽하게하기 위해 몇 가지 고려 사항을 추가했습니다.
Macke

9

모든 모범 사례에는 특정 이유가 있으며 이러한 질문에 대답하기 위해 참조 할 수 있습니다. 미래의 잠재적 변경 중 하나가 다른 구성 요소에 대한 변경도 포함 할 경우 항상 파급을 피해야합니다.

예를 들면 다음과 같습니다. 표준 인사말이 있다고 가정하고 모든 사람에게 동일하게 인사하려면 다음과 같이 작성하십시오.

def std_greeting(name):
    print "Hi, " + name

for name in ["Tom", "Mary"]:
    std_greeting(name)   # even the function call should be written only once

그렇지 않으면 변경 사항이있을 경우 두 곳에서주의를 기울이고 표준 인사말을 변경해야합니다.

그러나 "Hi"가 우연히 똑같고 인사말 중 하나를 변경해도 다른 인사말이 변경되는 것은 아니라면 별도로 유지하십시오. 따라서 다음과 같은 변경이있을 가능성이 있다고 생각할만한 논리적 이유가있는 경우에는 별도로 보관하십시오.

print "Hi, Tom"
print "Hello, Mary"

두 번째 부분에서는 함수에 "패키지"하는 양을 결정하는 것은 아마도 두 가지 요소에 달려 있습니다.

  • 가독성을 위해 블록을 작게 유지
  • 몇 블록 만 변경되도록 블록을 충분히 크게 유지하십시오. 호출 패턴을 추적하기 어렵 기 때문에 너무 많이 클러스터링 할 필요가 없습니다.

변화가 당신이 생각하는 것 발생할 때 이상적으로, "나는 다음 블록의 대부분을 변경해야합니다"가 아니라 "나는 코드를 변경해야 어딘가에 큰 블록 안에".


루프를 고려한 바보 같은 nitpicking-반복적으로 하드 코딩 된 소수의 이름 만 있으면되고 변경 될 것으로 기대하지 않는다면, 사용하지 않는 것이 더 읽기 쉽다고 주장합니다. 명부. for name in "Tom", "Mary": std_greeting(name)
yoniLavi

글쎄, 계속하려면 하드 코딩 된 목록이 코드 중간에 나타나지 않을 것이고 어쨌든 변수가 될 것이라고 말할 수 있습니다.) 그러나 그렇습니다.
Gerenuk

5

명명 된 상수 및 함수의 가장 중요한 측면은 입력량을 줄이는 것이 아니라 사용되는 다른 위치에 "연결"하는 것입니다. 귀하의 예와 관련하여 네 가지 시나리오를 고려하십시오.

  1. 같은 방식으로 두 인사말을 모두 변경해야합니다 (예 : Bonjour, TomBonjour, Mary]).

  2. 그것은 하나의 인사말을 변경할 수 있지만 [예와 같이 다른 사람을두고하는 것이 필요 Hi, Tom하고 Guten Tag, Mary].

  3. 그것은 다른 [예 모두 인사를 변경하는 것이 필요 Hello, TomHowdy, Mary].

  4. 인사말도 변경할 필요가 없습니다.

인사말을 변경할 필요가 없다면 어떤 방법을 사용하든 문제가되지 않습니다. 공유 기능을 사용하면 인사말을 변경해도 같은 방식으로 변경되는 자연스러운 효과가 있습니다. 모든 인사말이 같은 방식으로 변경되어야한다면 좋은 일입니다. 그러나 그렇지 않은 경우 각 사람의 인사말을 개별적으로 코딩하거나 지정해야합니다. 공통 기능이나 사양을 사용하도록 만든 모든 작업은 취소해야합니다 (처음에는 수행하지 않는 것이 좋습니다).

확실히 미래를 예측하는 것이 항상 가능하지는 않지만 두 인사말을 함께 변경해야 할 가능성이 높다고 생각되는 경우 공통 코드 (또는 명명 된 상수)를 사용하는 것이 유리합니다. ; 하나 또는 둘 다 변경하여 변경해야 할 가능성이 더 높다고 생각할만한 이유가있는 경우 이는 공통 코드를 사용하지 못하게하는 요인이됩니다.


나는이 대답을 좋아합니다 (적어도이 간단한 예에서는) 게임 이론을 통해 선택을 거의 계산할 수 있기 때문입니다.
RubberDuck

@RubberDuck : 일반적으로 어떤 것들이 "별칭 화"되어야하는지, 그렇지 않아야하는지에 대한 질문에 너무주의를 기울이지 않는다고 생각합니다. 많은 측면에서 버그의 가장 흔한 두 가지 원인은 (1) (2) 다른 것들이 첨부되어 있다는 것을 깨닫지 않고 무언가를 바꾸는 것 이러한 문제는 코드와 데이터 구조의 설계에서 발생하지만 개념에 많은 관심을 기울인 적이 있습니다. 앨리어싱은 일반적으로 무언가가 하나만 있거나 결코 바뀌지 않을 때는 중요하지 않지만 ...
supercat

@RubberDuck : ... 두 가지가 있고 일치하는 경우 하나를 변경하면 다른 하나의 값을 일정하게 유지 해야하는지 또는 관계를 일정하게 유지 해야하는지 여부를 알아야합니다 (다른 값은 변경됨). 나는 종종 값을 일정하게 유지하는 이점에 큰 비중을 두지 만 관계 의 중요성에 대한 가중치는 훨씬 적습니다 .
supercat

완전히 동의 해. 나는 기존의 지혜가 그것을 건조시키는 방법에 대해 생각하고 있었지만 통계적 으로 반복되는 코드가 나중에 코드를 반복하지 않을 가능성이 더 큽니다. 기본적으로 2 대 1 확률이 있습니다.
RubberDuck

@RubberDuck : 분리가 중요한 두 가지 시나리오를 제공하고 첨부가 더 나은 시나리오는 다양한 시나리오가 발생할 가능성에 대해 아무 말도하지 않습니다. 중요한 것은 두 개의 코드가 "우연히"일치 할 때 또는 동일한 기본 의미를 가지고 있기 때문에 인식하는 것입니다. 또한, 둘 이상의 아이템이있을 때, 하나 이상의 결과를 다른 것으로 만들고 싶어하지만 여전히 동일한 방식으로 둘 이상을 변경하는 추가 시나리오가 발생한다. 행동이 한 곳에서만 사용
되더라도

5

절차를 만들어야 할 이유가 있다고 생각합니다. 프로 시저를 작성해야하는 여러 가지 이유가 있습니다. 내가 가장 중요하게 생각하는 것은 :

  1. 추출
  2. 모듈성
  3. 코드 탐색
  4. 일관성 업데이트
  5. 재귀
  6. 테스트

추출

절차는 알고리즘의 특정 단계의 세부 사항에서 일반 알고리즘을 추출하는 데 사용될 수 있습니다. 적절하게 일관된 추상화를 찾을 수 있으면 알고리즘이하는 일에 대해 생각하는 방식을 구성하는 데 도움이됩니다. 예를 들어, 목록 표현을 반드시 고려할 필요없이 목록에서 작동하는 알고리즘을 설계 할 수 있습니다.

모듈성

절차를 사용하여 코드를 모듈로 구성 할 수 있습니다. 모듈은 종종 개별적으로 처리 될 수 있습니다. 예를 들어 별도로 빌드하고 테스트했습니다.

잘 설계된 모듈은 일반적으로 일관된 의미있는 기능 단위를 캡처합니다. 따라서 이들은 종종 다른 팀이 소유하거나 대안으로 완전히 대체하거나 다른 상황에서 재사용 할 수 있습니다.

코드 탐색

대규모 시스템에서는 특정 시스템 동작과 관련된 코드를 찾는 것이 어려울 수 있습니다. 라이브러리, 모듈 및 프로 시저 내에서 코드를 계층 적으로 구성하면이 문제를 해결할 수 있습니다. 특히 a) 의미 있고 예측 가능한 이름으로 기능을 구성하려는 경우 b) 유사한 기능과 관련된 절차를 서로 가까이 두십시오.

일관성 업데이트

대규모 시스템에서는 특정 동작 변경을 달성하기 위해 변경해야하는 모든 코드를 찾는 것이 어려울 수 있습니다. 프로그램의 기능을 구성하기 위해 절차를 사용하면이를보다 쉽게 ​​할 수 있습니다. 특히, 프로그램의 각 기능 비트가 한 번만 나타나고 응집력있는 절차 그룹에 나타나는 경우 (내 경험상) 업데이트하거나 일관성이없는 업데이트를해야 할 부분을 놓칠 가능성이 줄어 듭니다.

프로그램의 기능 및 추상화에 따라 절차를 구성해야합니다. 현재 두 비트의 코드가 동일한 지 여부에 따라 다릅니다.

재귀

재귀를 사용하려면 프로 시저를 작성해야합니다.

테스팅

일반적으로 절차를 서로 독립적으로 테스트 할 수 있습니다. 일반적으로 프로 시저의 첫 번째 부분을 두 번째 부분보다 먼저 실행해야하기 때문에 프로 시저 본문의 다른 부분을 독립적으로 테스트하는 것이 더 어렵습니다. 이 관찰은 종종 프로그램 행동 지정 / 확인에 대한 다른 접근법에도 적용됩니다.

결론

이러한 요점 중 다수는 프로그램의 이해와 관련이 있습니다. 절차는 도메인의 문제와 프로세스에 대해 조직하고, 쓰고, 읽을 수있는 도메인에 맞는 새로운 언어를 만드는 방법이라고 말할 수 있습니다.


4

당신이주는 두 가지 경우 모두 리팩토링 가치가있는 것처럼 보이지 않습니다.

두 경우 모두 명확하고 간결하게 표현 된 작업을 수행하며 일관성이없는 변경이 발생할 위험이 없습니다.

다음과 같은 경우 항상 코드를 분리하는 방법을 찾으십시오.

  1. 이 당신이 밖으로 분리 할 수있는 식별, 의미있는 작업이다,

  2. 어느 한 쪽

    에이. 작업을 표현하기가 복잡 하거나 ( 사용 가능한 기능을 고려하여)

    비. 작업이 두 번 이상 수행되므로 두 곳에서 동일한 방식으로 변경되도록하는 방법이 필요합니다.

조건 1이 충족되지 않으면 코드에 대한 올바른 인터페이스를 찾을 수 없습니다. 강제로 실행하려고하면 많은 매개 변수, 반환하려는 여러 항목, 많은 상황 논리가 생길 수 있습니다. 좋은 이름을 찾을 수 없습니다. 먼저 생각하고있는 인터페이스를 문서화 해보십시오. 기능의 올바른 번들링이라는 가설을 테스트하는 좋은 방법이며, 코드를 작성할 때 이미 사양화되고 문서화되어 있다는 추가 보너스가 제공됩니다!

2a없이 2a가 가능합니다 : 때로는 한 번만 사용된다는 것을 알았을 때도 흐름을 코드에서 가져 왔습니다. 단순히 다른 곳으로 옮기고 한 줄로 바꾸면 호출 된 컨텍스트가 갑자기 훨씬 더 읽기 쉽습니다. (특히 언어가 구현하기 어려운 개념이기 쉬운 경우). 추출 된 함수를 읽을 때 논리가 시작하고 끝나는 위치와 그 기능을 읽을 때도 명확합니다.

2b는 2a없이 가능합니다. 트릭은 어떤 종류의 변경이 더 가능성이 있는지 느끼고 있습니다. 회사 정책이 언제 어디서나 "안녕"이라고 말하거나 "안녕하세요"로 변경되거나 지불 요청이나 서비스 중단에 대한 사과를 보내면 인사말이보다 공식적인 분위기로 바뀔 가능성이 더 높습니까? 때로는 외부 라이브러리를 사용하고 있기 때문에 확실하지 않고 다른 라이브러리로 신속하게 교체하려는 경우가 있습니다. 기능이 작동하지 않더라도 구현이 변경 될 수 있습니다.

그러나 대부분 2a와 2b가 혼합되어 있습니다. 또한 코드의 비즈니스 가치, 코드 사용 빈도, 문서화 및 이해 여부, 테스트 스위트의 상태 등을 고려하여 판단해야합니다.

동일한 비트의 논리가 두 번 이상 사용 된 경우 리팩토링을 고려해야합니다. n대부분의 언어에서 들여 쓰기 수준 보다 더 많은 새 문장을 시작하는 경우 , 이는 약간 덜 중요한 또 다른 방아쇠입니다 ( n예를 들어 주어진 언어에 익숙한 값을 선택 하십시오 : 예를 들어 Python은 6 일 수 있습니다).

이러한 것들에 대해 생각하고 큰 스파게티 코드 괴물을 쓰러 뜨리는 한 괜찮을 것입니다. 테스트 나 문서와 같은 것들에 대해 그것이 필요하다면 시간이 말해 줄 것입니다.


3

일반적으로 Robert Harvey에 동의하지만 기능을 기능으로 분할하는 사례를 추가하고 싶었습니다. 가독성을 향상시킵니다. 사례를 고려하십시오.

def doIt(smth,smthElse)
    for x in getDataFromSomething(smth,smthElse):
        if not check(x,smth):
            continue
        process(x,smthElse)
        store(x) 

이러한 함수 호출이 다른 곳에서는 사용되지 않더라도 3 개의 함수가 모두 길고 중첩 루프 등이 있으면 가독성이 크게 향상됩니다.


2

적용해야 할 단단하고 빠른 규칙은 없으며 사용되는 실제 기능에 따라 다릅니다. 간단한 이름 인쇄의 경우 함수를 사용하지 않지만 수학 이야기 인 경우 다른 이야기가 될 것입니다. 그런 다음 함수가 두 번만 호출 되더라도 함수를 작성하면, 수학 합계가 변경 될 때 항상 동일하게 유지됩니다. 다른 예에서는 어떤 형태의 유효성 검사를 수행하는 경우 함수를 사용하므로 이름에서 5자를 초과하는 이름을 확인해야하는 경우 동일한 유효성 검사가 항상 수행되도록 함수를 사용합니다.

"두 줄 이상이 필요한 코드의 경우 func을 작성해야합니다."라고 말했을 때 자신의 질문에 대답했다고 생각합니다. 일반적으로 함수를 사용하지만 함수를 사용하여 부가가치가 있는지 판단하려면 자체 논리를 사용해야합니다.


2

나는 여기에 언급되지 않은 리팩토링에 대해 믿었습니다. 여기에 이미 많은 답변이 있다는 것을 알고 있지만 이것이 새로운 것이라고 생각합니다.

나는 용어가 발생하기 전에 무자비한 리 팩터이자 DRY를 강력하게 믿는 사람이었습니다. 대부분 머리에 큰 코드베이스를 유지하는 데 어려움이 있고 부분적으로 DRY 코딩을 즐기고 C & P 코딩에 대해 아무 것도 즐기지 않기 때문입니다. 실제로 그것은 고통스럽고 굉장히 느립니다.

문제는 DRY를 주장하면서 다른 기술을 거의 사용하지 않는 기술에 대해 많은 연습을 해주었습니다. 많은 사람들은 Java가 DRY를 만들기가 어렵거나 불가능하다고 주장하지만 실제로는 시도하지 않습니다.

오래 전에 나온 한 가지 예는 귀하의 예와 다소 유사합니다. 사람들은 자바 GUI 생성이 어렵다고 생각하는 경향이 있습니다. 물론 다음과 같이 코드를 작성하면됩니다.

Menu m=new Menu("File");
MenuItem save=new MenuItem("Save")
save.addAction(saveAction); // I forget the syntax, but you get the idea
m.add(save);
MenuItem load=new MenuItem("Load")
load.addAction(loadAction)

이것이 미쳤다고 생각하는 사람은 절대적으로 옳지 만 Java의 잘못은 아닙니다. 코드는 절대 이런 식으로 작성해서는 안됩니다. 이 메소드 호출은 다른 시스템에서 랩핑되도록 설계된 함수입니다. 그러한 시스템을 찾을 수 없다면 구축하십시오!

당신은 분명히 그렇게 코드를 작성할 수 없으므로, 물러서서 문제를 살펴 봐야합니다. 반복되는 코드가 실제로 무엇을하고 있습니까? 문자열과 관계 (트리)를 지정하고 해당 트리의 잎을 동작에 결합시킵니다. 그래서 당신이 정말로 원하는 것은 :

class Menu {
    @MenuItem("File|Load")
    public void fileLoad(){...}
    @MenuItem("File|Save")
    public void fileSave(){...}
    @MenuItem("Edit|Copy")
    public void editCopy(){...}...

간결하고 서술적인 방식으로 관계를 정의한 후에는이를 처리 할 메소드를 작성합니다.이 경우 전달 된 클래스의 메소드를 반복하고 트리를 작성한 다음이를 사용하여 메뉴를 빌드하십시오. 액션은 물론 메뉴를 표시합니다. 당신은 복제를 할 수 없으며, 당신의 방법은 재사용 할 수 있습니다 ... 그리고 아마도 많은 메뉴보다 쓰기가 쉬울 것입니다. 그리고 실제로 프로그래밍을 즐기고 있다면, 훨씬 더 재미있었습니다. 직접 작성해야하는 방법은 메뉴를 직접 작성하는 것보다 줄이 적습니다.

이 일을 잘하기 위해서는 연습을 많이해야합니다. 반복되는 부분에 고유 한 정보가 무엇인지 정확하게 분석하고 정보를 추출하여 잘 표현하는 방법을 알아 내야합니다. 문자열 구문 분석 및 주석과 같은 도구를 사용하는 방법을 배우면 많은 도움이됩니다. 오류보고 및 문서에 대해 명확하게 학습하는 것도 매우 중요합니다.

코드를 잘 작성하면 "무료"연습을 할 수 있습니다. 잘 익히면 재사용 가능한 도구 작성을 포함하여 DRY 코드 작성이 복사 및 붙여 넣기보다 빠르며 모든 오류, 중복 오류가 발생한다는 것을 알게 될 것입니다 코딩의 유형에 따라 어려운 변화.

드라이 테크닉과 도구를 최대한 연습하지 않으면 일을 즐길 수 없을 것 같아요. 복사 및 붙여 넣기 프로그래밍을하지 않아도되는 비용을 감수해야한다면 가져 가겠습니다.

내 요점은 다음과 같습니다.

  • 잘 리팩토링하는 방법을 모르면 복사 및 붙여 넣기에 더 많은 시간이 소요됩니다.
  • 가장 어렵고 사소한 경우에도 DRY를 주장함으로써 잘 리팩토링하는 법을 배웁니다.
  • 코드를 건조하게 만들기 위해 작은 도구가 필요한 경우 프로그래머입니다.

1

일반적으로 무언가를 함수로 나누면 코드의 가독성이 향상되거나 반복되지 않는 부분이 시스템의 핵심 요소 인 경우가 좋습니다. 위의 예에서 로케일에 따라 사용자에게 인사해야 할 경우 별도의 인사 기능을 갖는 것이 좋습니다.


1

함수를 사용하여 추상화를 생성하는 것에 대한 @DocBrown의 @RobertHarvey 의견에 대한 결론으로, 코드보다 훨씬 간결하거나 명확하지 않은 적절한 정보 함수 이름을 얻을 수 없다면 크게 추상화하지 않습니다. . 다른 적절한 이유가 없으면 기능을 수행 할 필요가 없습니다.

또한 함수 이름은 전체 의미를 거의 캡처하지 않으므로 일반적으로 익숙하지 않은 경우 정의를 확인해야 할 수도 있습니다. 특히 기능적 언어가 아닌 다른 언어에서는 부작용이 있는지, 어떤 오류가 발생할 수 있는지, 어떻게 대응하는지, 시간 복잡성, 리소스 할당 및 / 또는 해제 여부 및 스레드 여부를 알아야합니다. 안전한.

물론 여기서는 단순한 함수 만 고려하지만 정의 상으로는 두 가지 방식으로 진행됩니다. 이러한 경우 인라인이 복잡성을 추가하지는 않습니다. 또한 독자는 아마도 그것이 보일 때까지 간단한 기능이라는 것을 깨닫지 못할 것입니다. 정의에 하이퍼 링크로 연결되는 IDE를 사용하더라도 시각적으로 점프하는 것은 이해에 방해가됩니다.

일반적으로 코드를 더 작은 함수로 재귀 적으로 인수하면 함수가 더 이상 독립적 인 의미를 갖지 않는 지점으로 연결됩니다. 코드 조각이 작을수록 해당 조각은 컨텍스트에서 더 많은 의미를 갖기 때문입니다. 주변 코드로. 비유로 그림처럼 생각하십시오. 너무 가까이 확대하면보고있는 것을 알아낼 수 없습니다.


1
함수로 감싸는 가장 간단한 명령문이라도 더 읽기 쉽도록해야합니다. 함수에 래핑하는 것이 과도 할 때가 있지만 시간이 더 간결하고 분명한 함수 이름을 얻을 수 없다면 아마도 나쁜 징조 일 것입니다. 함수 사용에 대한 부정적인 점은 실제로 큰 문제는 아닙니다. 인라인에 대한 부정은 훨씬 더 큰 거래입니다. 인라이닝은 읽기 어렵고 유지 관리하기가 어렵고 재사용하기가 어렵습니다.
pllee

@ pllee : 극단적으로 취했을 때 함수를 고려하는 것이 비생산적인 이유를 제시했습니다 (질문은 일반적인 경우가 아니라 극단으로 가져 오는 것에 관한 것입니다.) 단순히 '달라야한다'고 주장하기 만하면됩니다. 명명 문제가 '아마도'나쁜 징후라고 생각하는 것은 여기에서 고려되는 경우에 피할 수 없다는 주장이 아닙니다 (물론 내 주장 적어도 경고입니다-당신이 단순히 요점에 도달했을 수 있음) 자체적으로 추상화.)
sdenham

나는 어디에서 "달라야한다"고 주장하는지 확실하지 않은 3 가지 부정적 언급을 언급했다. 내가 말하는 것은 이름을 만들 수 없다면 인라인이 너무 많은 것입니다. 또한 한 번 사용 된 작은 방법조차도 여기에 큰 요점이 없다고 생각합니다. 잘 명명 된 작은 메소드가 존재하므로 코드가 무엇을하려고하는지 알 필요가 없습니다 (IDE 점프 필요 없음). 예를 들어`if (day == 0 || day == 6)`vs`if (isWeekend (day))`의 간단한 문장조차도 읽기 쉽고 정신적으로 쉽게 표현됩니다. 이제 그 진술을 반복해야한다면 isWeekend아무 생각도하지 않아도됩니다 .
pllee

@pllee 거의 모든 반복되는 코드 조각에 대해 훨씬 짧고 명확한 이름을 찾을 수 없다면 그것은 "나쁜 징조"라고 주장하지만 그것은 믿음의 문제로 보입니다-당신은지지하는 주장을하지 않습니다. isWeekend는 의미있는 이름을 가지므로, 기준보다 더 짧거나 명확하지 않은 경우 기준에 실패 할 수있는 유일한 방법입니다. 그것이 당신이 후자라고 생각함으로써, 당신은 그것이 나의 입장에 반대되는 사례가 아니라고 주장하고 있습니다 (FWIW, 저는 isWeekend를 사용했습니다).
sdenham

한 줄짜리 문장조차도 더 잘 읽을 수있는 예제를 주었고 코드를 인라이닝하는 데 부정적인 영향을 미쳤습니다. 그것은 저의 겸손한 의견이며 제가 말하고있는 것은 아니며 "명명 할 수없는 방법"에 대해 논쟁하거나 말하지 않습니다. 나는 그 점에 대해 너무 혼란 스럽거나 왜 내 의견에 "이론적 증거"가 필요하다고 생각하는지 잘 모르겠습니다. 가독성 향상을 예로 들었습니다. 코드 덩어리가 변경되면 N 개의 스폿 (DRY에 대해 읽음)으로 변경해야하기 때문에 유지 관리가 더 어렵습니다. 코드를 재사용 할 수있는 방법으로 복사 붙여 넣기를 생각하지 않으면 재사용하기가 훨씬 어렵습니다.
pllee
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.